基础篇
redis安装
-
前置环境准备:自己购买云服务器或者使用VMWare本地虚拟机,通过
yum -y install gcc-c++
安装 C++ 库环境 -
下载:通过 redis 官网获取下载地址,打开终端
cd /opt
进入 opt 文件夹,通过 wget 命令下载安装包
-
解压:
tar -zxvf redis-7.0.11.tar.gz
-
安装:
cd redis-7.0.11
进入目录,执行 make 命令make && make install
,该过程需要一定的时间,出现以下结果表示安装成功
cd usr/local/bin
进入目录,ll
查看安装的目录
redis-benchmark:性能测试工具
redis-check-aof:修复有问题的AOF文件
redis-check-rdb:修复有问题的dump.rdb文件
redis-cli:客户端,操作入口
redis-sentinel:redis集群使用
redis-server:Redis服务器启动命令
-
配置文件修改:
cd /opt/redis-7.0.11
进入目录,mkdir /mconfig
新建目录,cp redis.conf /mconfig/redis7.conf
赋值配置文件到新建的文件目录下,cd mconfig
进入目录,vim redis7.conf
进行配置默认daemonize no 改为 daemonize yes
默认protected-mode yes 改为 protected-mode no
-
启动服务:在 /mconfig 目录下,
redis-server redis7.conf
使用修改后的配置文件启动服务 -
连接服务:
redis-cli -p 6379
连接redis 服务,ping 后返回 pong 说明服务连接成功
- 关闭服务:
shutdown
关闭服务
【扩展】常用 redis 配置文件说明
网络相关 | 说明 | 通用 | 说明 |
---|---|---|---|
bind 127.0.0.1 | 绑定的IP, 可通过设置 0.0.0.0 则所有IP可连接 | deamonize yes | 以守护进程(后台)的方式运行,默认是 no |
protected-mode yes | 保护模式,设置为yes,只允许我们在本机的回环连接,其他机器无法连接 | pidfile /var/run/redis_6379.pid | deamonize 为 yes 时生效,设置pid文件的目录 |
port 6379 | 端口设置 | loglevel notice | 日志级别 |
logfile ‘’ | 日志文件位置 | ||
持久化相关 | 说明 | databases 16 | 数据库的数量,默认是16个数据库 |
save 900 1 | 持久化频率设置,900s 内,至少有1个 key 进行了修改,将进行持久化操作 | always-show-logo | 启动时是否总是显示 LOGO |
stop-writes-on-bgsave-error yes | 持久化如果出错,是否还需要继续工作 默认为 yes | ||
rdbcompression yes | 是否压缩 rdb 文件,需要消耗 CPU 资源 | 安全相关 | 说明 |
rdbchecksum yes | 保存rdb文件的时候,进行错误校验 | maxclients 10000 | 设置能连接上 Redis 的最大客户端的数量 |
dir ./ | rdb 文件保存的目录 | maxmemory | redis配置最大的内存容量 |
appendonly no | aof模式是否开启,默认是不开启使用rdb方式持久化的 | maxmemory-policy noeviction | Redis的内存淘汰策略 |
appendfilename “appendonly.aof” | aof 持久化的文件的名字 | ||
appendfsync | aof 持久化策略。 |
Redis的内存淘汰策略有哪些:
-
noeviction:当内存不足以容纳新写入数据时,新写入操作会报错;
-
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的);
-
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。设置过期时间的键空间选择性移除;
-
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key;
-
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key;
-
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
常用数据类型指令
redis7 提供了10种数据类型,分别是:
- String:一个key对应一个value,value可以包含任何数据如jpg图片或者序列化的对象 ,value最多可以是512M
- List:简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边),底层实际是个双端链表,最多可以包含 2^32 - 1 个元素
- Hash: string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象,每个 hash 可以存储 2^32 - 1 键值对
- Set:String 类型的无序集合,集合成员是唯一的,底层通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1),集合中最大的成员数为 2^32 - 1
- ZSet:有序集合与普通集合set非常相似,不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员,集合的成员是唯一的,但是评分可以是重复了
- GEO:用于存储地理位置信息,并对存储的信息进行操作,包括添加地理位置的坐标 / 获取地理位置的坐标 / 计算两个位置之间的距离 / 用户给定的经纬度坐标来获取指定范围内的地理位置集合
- HyperLogLog:用来做基数统计
- BitMap:位图,由0和1状态表现的二进制位的bit数组
- Bitfield:一次性对多个比特位域(指连续的多个比特位)进行操作
- Stream:主要用于消息队列,提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失
数据的操作不外乎增删改查,汇整前 5 个数据类型的操作指令
Key 表示键都适用
增相关指令:
数据类型 | 增指令 | 说明 | RedisTemplate |
---|---|---|---|
String | set | 添加键值对 | redisTemplate.opsForValue().set |
String | mset [ …] | 设置多个 key 的值为各自对应的 value | redisTemplate.opsForValue().multiSet |
String | msetnx [ …] | 当且仅当所有给定键都不存在时, 为所有给定键设置值。即使只有一个给定键已经存在,该命令也会拒绝执行对所有键的设置操作。 | redisTemplate.opsForValue().multiSetIfAbsent |
String | setnx | 当数据库中key不存在时,可以将key-value添加数据库 | redisTemplate.opsForValue().setIfAbsent |
String | setex | 添加键值对,并设置超时时间 | redisTemplate.opsForValue().set |
List | lpush | 将一个或多个值插入到列表key 的头部 | redisTemplate.opsForList().leftPush / redisTemplate.opsForList().leftPushAll |
List | lpushx | 在当 key 存在并且存储着一个 list 类型值的时候,向值 list 的头部插入 value。 与 LPUSH 相反,当 key 不存在的时候不会进行任何操作。 | redisTemplate.opsForList().leftPushIfPresent |
List | rpush | 存储在 key 中的列表的尾部插入所有指定的值 | redisTemplate.opsForList().rightPush / redisTemplate.opsForList().rightPushAll |
List | rpushx | 将值 value 插入到列表 key 的表尾, 当且仅当 key 存在并且是一个列表。 和 RPUSH 命令相反, 当 key 不存在时,RPUSHX 命令什么也不做 | redisTemplate.opsForList().rightPushIfPresent |
List | linsert before|after | 把 element 插入到列表 key 中参考值 pivot 的前面或后面 | redisTemplate.opsForList().leftPush / redisTemplate.opsForList().rightPush |
Hash | hset [ …] | 存储在 key 中的哈希表的 field 字段赋值 value | redisTemplate.opsForHash().put |
Hash | hmset [ …] | 用于同时将多个 field-value (字段-值)对设置到哈希表中 | redisTemplate.opsForHash().putAll |
Hash | hsetnx | 用于为哈希表中不存在的字段赋值。如果字段已经存在于哈希表中,操作无效 | redisTemplate.opsForHash().putIfAbsent |
Set | sadd [ …] | 将一个或多个成员元素加入到集合中 | redisTemplate.opsForSet().add |
ZSet | zadd [ …] | 用于将一个或多个 member 元素及其 score 值加入到有序集 key | redisTemplate.opsForZSet().add / redisTemplate.opsForZSet().addIfAbsent |
查相关指令:
数据类型 | 查指令 | 说明 | RedisTemplate |
---|---|---|---|
Key | ttl | 返回 key 的剩余过期时间 | |
Key | keys | 查找所有匹配给定模式 pattern 的 key | |
Key | exists [ …] | 用于检查给定 key 是否存在 | |
String | get | 查询对应键值 | redisTemplate.opsForValue().get |
String | getrange | 根据 start 和 end 值返回存储在 key 中的字符串的子串,能取到头尾 | redisTemplate.opsForValue().get |
String | mget [ …] | 返回所有(一个或多个)给定 key 的值 | redisTemplate.opsForValue().multiGet |
String | strlen | 获取指定 key 所储存的字符串值的长度 | redisTemplate.opsForValue().size |
List | lindex | 返回列表 key 里索引 index 位置存储的元素 | redisTemplate.opsForList().index |
List | lrange
| 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定 | redisTemplate.opsForList().range |
List | lpos [ RANK ] [COUNT ] [MAXLEN ] | 返回列表中匹配元素的索引 | redisTemplate.opsForList().indexOf / redisTemplate.opsForList().lastIndexOf |
List | llen | 返回存储在 key 中的列表长度 | redisTemplate.opsForList().size |
Hash | hexists | 查看哈希表的指定字段field 是否存在 | redisTemplate.opsForHash().hasKey |
Hash | hget | 返回哈希表中指定字段 field 的值 | redisTemplate.opsForHash().get |
Hash | hmget [ …] | 返回哈希表中,一个或多个给定字段(field)的值 | redisTemplate.opsForHash().multiGet |
Hash | hgetall | 返回存储在 key 中的哈希表中所有的域和值 | redisTemplate.opsForHash().entries |
Hash | hkeys | 返回存储在 key 中哈希表的所有域(field ) | redisTemplate.opsForHash().keys |
Hash | hvals | 返回哈希表所有域(field)的值 | redisTemplate.opsForHash().values |
Hash | hlen | 获取哈希表中字段(fields)的数量 | redisTemplate.opsForHash().size |
Hash | hstrlen | 返回存储在 key 中的哈希表里, 与给定域 field 相关联的值的字符串长度 | redisTemplate.opsForHash().lengthOfValue |
Hash | hscan [MATCH ] [COUNT ] | 用于遍历哈希表中的键值对 | redisTemplate.opsForHash().scan |
Set | scard | 返回集合中元素的数量 | redisTemplate.opsForSet().size |
Set | sdiff [ …] | 返回第一个集合与其他集合之间的差异 | redisTemplate.opsForSet().difference |
Set | sdiffstore [ …] | 作用和SDIFF 类似,不同的是它将结果保存到 destination 集合,而把结果集返回给客户端。 | redisTemplate.opsForSet().differenceAndStore |
Set | sinter [ …] | 返回所有给定集合的成员交集 | redisTemplate.opsForSet().intersect |
Set | sunion [ …] | 返回所有给定集合的并集 | redisTemplate.opsForSet().union |
Set | smembers | 返回存储在 key 中的集合的所有的成员 | redisTemplate.opsForSet().members |
Set | sismenber | 用于判断元素 member 是否集合 key 的成员 | redisTemplate.opsForSet().isMember |
Set | srandmenber [] | 随机返回集合key 中的一个(多个)随机元素 | redisTemplate.opsForSet().randomMember / redisTemplate.opsForSet().randomMembers / redisTemplate.opsForSet().distinctRandomMembers |
Set | sscan [MATCH ] [COUNT ] | 用于遍历集合中键的元素,SSCAN 继承自SCAN | redisTemplate.opsForSet().scan |
ZSet | zcard | 返回有序集的成员个数 | redisTemplate.opsForZSet().size |
ZSet | zscore | 返回有序集 key .中成员 member 的分数 | redisTemplate.opsForZSet().score |
ZSet | zcount | 返回有序集 key 中, score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量 | redisTemplate.opsForZSet().count |
ZSet | zrange
| 返回有序集key 中,指定区间内的成员,其中成员的按分数值递增(从小到大)来排序,具有相同分数值的成员按字典序(lexicographical order )来排列。 | redisTemplate.opsForZSet().range |
ZSet | zrevrange
| 返回有序集key 中,指定区间内的成员。其中成员的位置按score值递减(从高到低)来排列 | redisTemplate.opsForZSet().reverseRange |
ZSet | zrank | 返回有序集key 中成员member 的排名,排名从0开始 | redisTemplate.opsForZSet().rank |
ZSet | zscan [MATCH ] [COUNT ] | 用于遍历集合中键的元素 | redisTemplate.opsForZSet().scan |
改相关指令
数据类型 | 改指令 | 说明 | RedisTemplate |
---|---|---|---|
Key | expire | 设置 key 的过期时间(seconds) | |
String | getset | 将键 key 的值设为 value , 并返回键的旧值 | redisTemplate.opsForValue().getAndSet |
String | setrange | 从偏移量 offset 开始, 用 value 参数覆盖键 key 储存的字符串值 | redisTemplate.opsForValue().set |
String | append | 用于为指定的 key 追加值 | redisTemplate.opsForValue().append |
String | incr | 将 key 中储存的数字值增一,字符串类型的值需表示为数字 | redisTemplate.opsForValue().increment |
String | incrby | key 中储存的数字加上指定的增量值 | redisTemplate.opsForValue().increment |
String | decr | 为键 key 储存的数字值减去一 | redisTemplate.opsForValue().decrement |
String | decrby | 将键 key 储存的整数值减去减量 decrement | redisTemplate.opsForValue().decrement |
List | lset | 设置列表 key 中 index 位置的元素值为 element | redisTemplate.opsForList().set |
Hash | hincrby | 为哈希表 key 中的域 field 的值加上增量 increment | redisTemplate.opsForHash().increment |
ZSet | zincrby | 为有序集 key 的成员 member 的 score 值加上增量 increment | redisTemplate.opsForZSet().incrementScore |
删相关指令
数据类型 | 删指令 | 说明 | RedisTemplate |
---|---|---|---|
Key | del | 删除给定的一个或多个 key 。 | |
List | ltrim
| 修剪(trim)一个已存在的 list,这样 list 就会只包含指定范围的指定元素 | redisTemplate.opsForList().trim |
List | lpop [] | 删除并返回存储在 key 中的列表的第一个元素或者前 count 个元素 | redisTemplate.opsForList().leftPop |
List | rpop [] | 用于移除并返回列表 key 的最后一个元素或者后 count 个元素 | redisTemplate.opsForList().rightPop |
List | lrem | 用于从列表 key 中删除前 count 个值等于 element 的元素 | redisTemplate.opsForList().remove |
Hash | hdel [ …] | 用于删除哈希表 key 中的一个或多个指定字段 | redisTemplate.opsForHash().delete |
Set | spop [] | 从集合 key 中删除并返回一个或多个随机元素 | redisTemplate.opsForSet().pop |
Set | smove | 用于从集合source 中移动成员member 到集合 destination 。 | redisTemplate.opsForSet().move |
Set | srem [ …] | 用于在集合中删除指定的元素 | redisTemplate.opsForSet().remove |
ZSet | zpopmax [] | 删除并返回最多count 个有序集合key 中的最高得分的成员 | |
ZSet | zpopmin [] | 删除并返回最多count 个有序集合key 中最低得分的成员 | |
ZSet | zrem [ …] | 从有序集合key 中删除指定的成员member | redisTemplate.opsForZSet().remove |
更多相关指令可以通过 redis 命令手册 或者 Commands | Redis 查询
redis持久化
Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制
RDB
RDB(Redis DataBase)是Redis默认的持久化方式。按照一定的时间开启一个子进程将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期
redis 6.0 之前的持久化配置
redis 6.2 以上
除了自动触发外,使用 save 和 bgsave 可以手动触发持久化。save 在主程序中执⾏会阻塞当前redis服务器,直到持久化工作完成执行save命令期间,Redis不能处理其他命令,线上禁止使用。bgsave Redis会在后台异步进行快照操作,不阻塞快照同时还可以响应客户端请求,该触发方式会fork一个子进程由子进程复制持久化过程。
优点:
- 适合大规模的数据恢复
- 按照业务定时备份
- 对数据完整性和一致性要求不高
- RDB 文件在内存中的加载速度要比 AOF 快得多
缺点:
- 在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失从当前至最近一次快照期间的数据,快照之间的数据会丢失
- 内存数据的全量同步,如果数据量太大会导致I/0严重影响服务器性能
- RDB依赖于主进程的fork,在更大的数据集中,这可能会导致服务请求的瞬间延迟。fork的时候内存中的数据被克隆了一份,大致2倍的膨胀性,需要考虑
触发RDB持久化的方式:
- 配置文件中默认的快照配置
- 手动save/bgsave命令
- 执行flushall/flushdb命令也会产生dump.rdb文件,但里面是空的,无意义
- 执行shutdown且没有设置开启AOF持久化
- 主从复制时,主节点自动触发
AOF
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件。redis启动之初会读取该文件重新构建数据,换言之, redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
默认情况下,redis是没有开启AOF(append only file)的。开启AOF功能需要设置配置:appendonly yes。
aof 工作流程:
- Client 源源不断接收请求命令
- 命令到达 Redis Server 将命令先放入AOF缓存中进行保存。这里的AOF缓冲区实际上是内存中的一片区域,存在的目的是当这些命令达到一定量以后再写入磁盘,避免频繁的磁盘IO操作。
- OF缓冲会根据AOF缓冲区配置文件的策略将命令写入磁盘上的AOF文件
- 随着写入AOF内容的增加为避免文件膨胀,会根据规则进行命令的合并(又称AOF重写),从而起到AOF文件压缩的目的
- 当Redis Server 服务器重启的时候会从AOF文件载入数据
aof 持久化策略:
- always: 每次修改都会 sync。消耗性能;
- everysec(默认): 每秒执行一次 sync,可能会丢失1秒的数据;
- no :不执行 sync, 这个时候操作系统自己同步数据,速度最快
aof 重写
同时满足以下两个条件会自动触发重写
- 根据上次重写后的aof大小,判断当前aof大小是不是增长了1倍
- 重写时满足的文件大小
发送 bgrewriteaof 命令手动触发重写
redis 事务
数据库中的事务是指对数据库执行一批操作,在同一个事务当中,这些操作最终要么全部执行成功,要么全部失败,不会存在部分成功的情况。
redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的,Redis会将一个事务中的所有命令序列化,然后按顺序执行。
# 正常执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 v1
QUEUED
127.0.0.1:6379(TX)> set key1 v2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
# 放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 v1
QUEUED
127.0.0.1:6379(TX)> discard
OK
# 在一个事务中的命令出现错误(编译出错),那么所有的命令都不会执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 v1
QUEUED
127.0.0.1:6379(TX)> set key2
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
# 在一个事务中出现运行错误,那么正确的命令会被执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 v1
QUEUED
127.0.0.1:6379(TX)> incr key1
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
watch命令是一种乐观锁的实现,Redis在修改的时候会检测数据是否被更改,如果更改了,则执行失败
第一个窗口蓝色框第5步执行结果返回为空,也就是相当于是失败
redis 事务特性
事务特性 | 说明 | 是否支持 |
---|---|---|
原子性(Atomicity) | Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。 | 否 |
一致性(Consistency) | Redis会保证一个事务内的命令依次执行,而不会被其它命令插入 | 是 |
隔离性(Isolation) | Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。 | 是 |
持久性(Durability) | 当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有持久性。 | 是 |
redis 主从复制
对于缓存来说,一般都是用来支撑读高并发的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。
主从复制有两种实现模式:1主n从和链式连接
1主n从
链式连接:上一个slave可以是下一个slave的master, slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻主master的写压力
一主二从模式演示
-
配置文件修改
# 复制 2 分配置文件作为从机 [root@localhost mconfig]# cp redis7.conf redis80.conf [root@localhost mconfig]# cp redis7.conf redis81.conf # 修改 redis80.conf 配置信息 [root@localhost mconfig]# vim redis80.conf daemonize yes pidfile /var/run/redis_6380.pid dbfilename dump80.rdb # 修改 redis81.conf 配置信息 [root@localhost mconfig]# vim redis81.conf daemonize yes pidfile /var/run/redis_6381.pid dbfilename dump81.rdb
-
主从复制手动配置
启动并连接到服务,在从机上执行
slaveof 地址 端口号
,通过info replication
获得信息
-
各种情况演示
从机是否可以写:否
从机down机后,master 写入,从机重新连接后(配置文件未配置主从关系)是否能连上主机:否
主机shutdown后从机会变为主机吗:否
主机shutdown后,从机能否读,主机重启后,关系是否还在:是
主机shutdown后,从机能否变成主机:是
主从复制流程
- 当从库和主库建立MS关系后,会向主数据库发送SYNC命令
- 主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来
- 当快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis
- 从Redis接收到后,会载入快照文件并且执行收到的缓存的命令
- 之后,主Redis每当接收到写命令时就会将命令发送从Redis,从而保证数据的一致
redis 哨兵
哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能:
-
集群监控:负责监控 redis master 和 slave 进程是否正常工作。
-
消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
-
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
-
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作
哨兵模式演示
-
配置文件修改:自动定义主从关系
# 重新定义 redis79.conf 配置信息 [root@localhost mconfig]# vim redis79.conf daemonize yes # bind 172.0.0.1 # 注释掉此项配置 protected-mode no # 指定端口 port 6379 # 指定RDB保存目录 dir /mconfig pidfile /var/run/redis_6379.pid # 设置密码(主从机保持一致) requirepass 111111 dbfilename dump79.rdb # 定义 redis80.conf 配置信息 [root@localhost mconfig]# vim redis80.conf daemonize yes # bind 172.0.0.1 # 注释掉此项配置 protected-mode no # 指定端口 port 6380 # 指定RDB保存目录 dir /mconfig pidfile /var/run/redis_6380.pid # 设置密码(主从机保持一致) requirepass 111111 dbfilename dump80.rdb # 自动配置主机和密码 replicaof 192.168.186.130 6379 masterauth 111111 # 定义 redis81.conf 配置信息 [root@localhost mconfig]# vim redis81.conf daemonize yes # bind 172.0.0.1 # 注释掉此项配置 protected-mode no # 指定端口 port 6381 # 指定RDB保存目录 dir /mconfig pidfile /var/run/redis_6381.pid # 设置密码(主从机保持一致) requirepass 111111 dbfilename dump81.rdb # 自动配置主机和密码 replicaof 192.168.186.130 6379 masterauth 111111
-
哨兵配置文件
3个哨兵都同时在192.168.186.130这台机器进行配置,配置说明
# 设置要监控的master服务器,quorum表示最少有几个哨兵认可客观下线,同意故障迁移的法定票数。 sentinel monitor <master-name> <ip> <redis-port> <quorum> # 如果master设置了密码,配置连接master服务的密码 sentinel auth-pass <master-name> <password> # 指定多少毫秒之后,主节点没有应答哨兵,此时哨兵主观上认为主节点下线 sentinel down-after-milliseconds <master-name> <milliseconds> # 表示允许并行同步的slave个数,当Master挂了后,哨兵会选出新的Master,此时,剩余的slave会向新的master发起同步数据 sentinel parallel-syncs <master-name> <nums> # 故障转移的超时时间,进行故障转移时,如果超过设置的毫秒,表示故障转移失败 sentinel failover-timeout <master-name> <milliseconds> # 配置当某一事件发生时所需要执行的脚本 sentinel notification-script <master-name> <script-path> # 客户端重新配置主节点参数脚本 sentinel client-reconfig-script <master-name> <script-path>
哨兵配置项创建:
# sentinel79.conf bind 0.0.0.0 daemonize yes protected-mode no port 26379 logfile "/mconfig/log/sentinel79.log" pidfile /var/run/redis-sentinel26379.pid dir /mconfig sentinel monitor mymaster 192.168.186.130 6379 2 sentinel auth-pass mymaster 111111 # sentinel80.conf bind 0.0.0.0 daemonize yes protected-mode no port 26380 logfile "/mconfig/log/sentinel80.log" pidfile /var/run/redis-sentinel26380.pid dir /mconfig sentinel monitor mymaster 192.168.186.130 6379 2 sentinel auth-pass mymaster 111111 # sentinel81.conf bind 0.0.0.0 daemonize yes protected-mode no port 26381 logfile "/mconfig/log/sentinel81.log" pidfile /var/run/redis-sentinel26381.pid dir /mconfig sentinel monitor mymaster 192.168.186.130 6379 2 sentinel auth-pass mymaster 111111
-
启动哨兵
redis-sentinel sentinel79.conf --sentinel
,所有 redis 相关的进程
哨兵启动后的配置和日志信息(主机ip地址因电脑重启导致变动)
哨兵启动后会在日志和配置文件中添加主从机的信息和 sentinel ID
- 主机 master shutdown
从哨兵日志看,从主机 down 机到重新选出主机会经历三个步骤:
- 主机下线确认(黄色 mark):会有 sdown 和 odown 两次确认。sdown(主观不可用)是单个sentinel自己主观上检测到的关于 master 的状态,从 sentinel 的角度来看,如果发送了 ping 心跳后,在一定时间内没有收到合法的回复,就达到了 sdown的条件。odown需要一定数量的 sentinel,多个哨兵达成一致意见才能认为一个 master 客观上已经宕掉。
- 选举出领导者哨兵(绿色 mark):当主节点被判断客观下线以后,各个哨兵节点会进行协商,先选举出一个领导者哨兵节点(兵王)并由该领导者节点,也即被选举出的兵王进行 failover (故障迁移)
- 领导者哨兵选出新的master:依据一定规则选出 master 主机,sentinel leader 执行 slaveof no one 命令让选出来的从节点成为新的主节点,并通过 slaveof 命令让其他节点(包括原来的主节点)成为其从节点
【扩展】Raft 算法和 master 选取规则
监视该主节点的所有哨兵都有可能被选为领导者,选举使用的算法是Raft算法。Raft算法的基本思路是先到先得:即在一轮选举中,哨兵A向B发送成为领导者的申请,如果B没有同意过其他哨兵,则会同意A成为领导者
首先根据 redis.conf 配置文件中,选出优先级 slave-priority 或者 replica-priority 最高的从节点(数字越小优先级越高 ),如果都相同,接着判断复制偏移位 offset 最大的从节点,如果还相同,按照字典顺序(ASCII码)选择最小 Run ID 的从节点。
redis 集群
Redis集群是一个提供在多个Redis节点间共享数据的程序集。集群具有以下特性:
- 可以支持多个 master,每个 master 可以有多个 slave 。
- 由于Cluster自带Sentinel的故障转移机制,内置了高可用的支持,无需再去使用哨兵功能。
- 客户端与Redis的节点连接,不再需要连接集群中所有的节点,只需要任意连接集群中的一个可用节点即可。
- 槽位 slot 负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽和数据之间的关系。
哈希槽分区
集群的 key 空间被分成 16384 个槽,有效地设置了 16384 个主节点的集群大小上限(但是,建议的最大节点大小约为 1000个节点)。集群中的每个主节点处理 16384 个哈希槽的一个子集。 当没有集群重新配置正在进行时(即哈希槽从一个节点移动到另一个节点),集群是稳定的。 当集群稳定时,单个哈希槽将由单个节点提供服务(但是,服务节点可以有一个或多个副本,在网络分裂或故障的情况下替换它,并且可以用于扩展读取陈旧数据是可接受的操作)。
哈希槽分区是一种将数据分散存储在多个节点上的技术,它将所有可能的键映射到一个固定数量的哈希槽中。每个节点负责处理其中一部分哈希槽,这样就可以将数据分布在多个节点上,从而提高了系统的可扩展性和容错性。当需要添加或删除节点时,Redis 会自动将哈希槽重新分配到新的节点上,以保持负载均衡。这种分区技术使得 Redis 可以处理大量的数据,并且可以在不停机的情况下进行扩展和缩减。
哈希槽被分为 16384 个槽位有以下几个原因:
- 更小的心跳包大小:redis 节点需要发送一定数量的 ping 消息作为心跳包,在消息头中最占空间的是 myslots[CLUSTER_SLOTS/8],当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb ;在消息头中最占空间的是myslots[CLUSTER_SLOTS/8], 当槽位为16384时,这块的大小是: 16384÷8÷1024=2kb 。
- redis 的集群主节点数量基本不可能超过1000个:集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者不建议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。
- 槽位越小,节点少的情况下,压缩比高,容易传输:Redis主节点的配置信息中它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。 如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。
3主3从 redis 集群演示:
- 虚拟机配置
-
6个 redis 实例配置文件
cd /mconfig
进入目录下mkdir cluster
新建 cluster 目录,vim cluster81.conf
# cluster81.conf bind 0.0.0.0 daemonize yes protected-mode no port 6381 logfile "/mconfig/log/cluster6381.log" pidfile /mconfig/cluster6381.pid dir /mconfig/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
其他5台只需要修改下端口号相关的配置
虚拟机 node1 node2 192.168.186.131 6381 6382 192.168.186.132 6383 6384 192.168.186.133 6385 6386 -
启动实例并构建主从关系
redis-server cluster81.conf
启动部署的实例# --cluster-replicas 1 表示为每个master创建一个slave节点 redis-cli -a 111111 --cluster create --cluster-replicas 1 192.168.186.131:6381 192.168.186.131:6382 192.168.186.132:6383 192.168.186.132:6384 192.168.186.133:6385 192.168.186.133:6386
选择yes,在这过程中可能会出现卡在 waiting for the cluster to join 上,可以通过 systemctl stop firewalld.service
关闭防火墙或者通过 firewall-cmd --zone=public --add-port=80/tcp --permanent (permanent永久生效,没有此参数重启后失效)
开放端口号
redis-cli -a 111111 -p 6381 -c
连接客户端,加上 -c 是防止路由失效。通过 info replication
cluster info
cluster nodes
查看集群状态
当往 redis 添加 key 值时会根据哈希槽计算映射到对应的 redis master 中去,cluster keyslot
查看key所在槽位
-
容错切换
当某台 master down机后,自动将从机变成主机,原来的主机恢复后仍然为从机,执行
cluster failover
后恢复之前的主从关系
-
服务扩容
准备工作:新建虚拟机,创建配置文件启动服务
服务扩容主要有以下三步:
```bash
# 1. 将新增的 6387 作为 master 节点加入原有集群
# redis-cli -a 密码 --cluster add-node 自己实际IP地址:6387 自己实际IP地址:6381
redis-cli -a 111111 --cluster add-node 192.168.186.134:6387 192.168.186.131:6381
# 2. 重新分派槽号
# redis-cli -a 密码 --cluster reshard IP地址:端口号
redis-cli -a 111111 --cluster reshard 192.168.186.134:6387
# 3. 添加 slave 节点
# 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.186.134:6388 192.168.186.134:6387 --cluster-slave --cluster-master-id c9e87379e1aa142ac06a18165e0ed9c061a2cf36
```
-
服务缩容
服务缩容可以分为以下几步:
# 从集群中删除 slave 节点 # redis-cli -a 密码 --cluster del-node ip:从机端口 从机节点ID redis-cli -a 111111 --cluster del-node 192.168.186.134:6388 f76af4e8c9a4c91642cdd3e95e8dd55810bd13c9 # 清空 master 节点槽号,将槽号重新分配给其他节点 # redis-cli -a 密码 --cluster reshard IP地址:端口号 redis-cli -a 111111 --cluster reshard 192.168.186.131:6381 # 从集群中删除 mater 节点 # redis-cli -a 密码 --cluster del-node ip:主机端口 主机节点ID redis-cli -a 111111 --cluster del-node 192.168.186.134:6387 c9e87379e1aa142ac06a18165e0ed9c061a2cf36
redis 线程
Redis的主线程是单线程的,它使用事件驱动模型和I/O多路复用程序来处理客户端请求,而无需为每个客户端请求创建新线程。
redis4.0 前 | redis 6.0后 | |
---|---|---|
线程模型 | ||
说明 | I/O 的读和写本身是堵塞的,比如当 socket 中有数据时,Redis 会通过调用先将数据从内核态空间拷贝到用户态空间,再交给 Redis 调用,而这个拷贝的过程就是阻塞的,当数据量越大时拷贝所需要的时间就越多,而这些操作都是基于单线程完成的 | 从Redis6开始,新增了多线程的功能来提高 I/O 的读写性能,主要实现思路是将主线程的 IO 读写任务拆分给一组独立的线程去执行,这样就可以使多个 socket 的读写可以并行化了,采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),将最耗时的Socket的读取、请求解析、写入单独外包出去,剩下的命令执行仍然由主线程串行执行并和内存的数据交互。 |
文件事件处理器
Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。文件事件分派器队列的消费是单线程的。
- 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
- 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。
I/O 多路复用
由于读写操作等待用户输入或输出都是阻塞的,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现。 I/O 多路复用机制,就是我们说的select,poll,epoll。就是通过其中一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。可以基于一个阻塞对象并同时在多个描述符上等待就绪,而不是使用多个线程(每个文件描述符一个线程,每次new一个线程),这样可以大大节省系统资源。
简单说,I/O 多路复用的特点是通过 select,poll,epoll 其中一种机制,使得一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select,poll,epoll 等函数就可以返回。
文件描述符
文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
SpringBoot 整合 redis
pom 引入
<!--SpringBoot与Redis整合依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置文件
# ========================redis单机=====================
spring.redis.database=0
# 修改为自己真实IP
spring.redis.host=192.168.186.131
spring.redis.port=6379
spring.redis.password=111111
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
# ========================redis集群=====================
spring.redis.password=111111
# 获取失败 最大重定向次数
spring.redis.cluster.max-redirects=3
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
#支持集群拓扑动态感应刷新,自适应拓扑刷新是否使用所有可用的更新,默认false关闭
spring.redis.lettuce.cluster.refresh.adaptive=true
#定时刷新
spring.redis.lettuce.cluster.refresh.period=2000
spring.redis.cluster.nodes=192.168.186.131:6381 192.168.186.131:6382 192.168.186.132:6383 192.168.186.132:6384 192.168.186.133:6385 192.168.186.133:6386
序列化问题配置
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis 序列化配置
*
* @author pc
* @date 2023/04/26
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 1.创建 redisTemplate 模板
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 2.关联 redisConnectionFactory
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 3. 创建序列化类
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 4.设置可见度
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 5.启动默认的类型
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
// 6.序列化类,对象映射设置
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 7.设置value的转化格式和key的转换格式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key 和 hash key 序列化方式采用 string
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// value 和 hash value 序列化方式采用 jackson
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
封装
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* redis缓存
*
* @author pc
* @date 2023/02/22
*/
@Component
public class RedisCache {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(String key, T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 过期时间
* @param timeUnit 时间单位
*/
public<T> void setCacheObject(String key, T value, Integer timeout, TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 获取缓存的对象
*
* @param key 缓存的键值
* @return {@link Object}
*/
public<T> T getCacheObject(String key){
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key key
*/
public boolean deleteObject(String key)
{
return redisTemplate.delete(key);
}
}
实践篇
待补充