Redis 学习笔记-上【基础篇】

Redis 基础学习


1. Redis简介

1.1 Redis主流功能和应用

①分布式缓存,挡在mysql之前 “护卫”。

②内存存储和持久化(RDB+AOF)。

redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务

③高可用架构搭配(单机、主从、哨兵、集群)

④缓存穿透、击穿、雪崩

⑤分布式锁

⑥队列

⑦排行榜+点赞

⑧…

1.2 Redis的优势

①性能极高

②数据类型丰富,不仅支持简单的key-value类型数据,同时还提供list,set,zset,hash等数据结构的存储

③支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用

④支持数据的备份,即master-slave模式的数据备份


2. Redis安装

2.1 官网地址

下载地址:Downloads - Redis

中文文档:redis中文文档

Redis在线练习:https://try.redis.io/

参考手册:http://doc.redisfans.com/

2.2 Linux版安装 (redis-7.2)

2.2.1 配置gcc环境

检查有没有gcc:gcc -v

若没有,安装gcc:yum -y install gcc-c++

2.2.2 安装redis

解压压缩包至 /opt 目录下

安装:make && make install

(默认安装目录:usr/local/bin

若缺少 jemalloc 库:make MALLOC=libc

(出现Hint: It's a good idea to run 'make test' ;),则安装成功)

redis-benchmark:性能测试工具,服务启动后运行该命令
redis-check-aof:修复有问题的AOF文件
redis-check-rdb:修复有问题的rdb文件
redis-cli:客户端,操作入口
redis-sentinel:redis集群使用
redis-server:redis服务器启动命令

2.2.3 配置文件

配置文件在 /opt/redis-7.2/redis.conf

是先拷贝一份默认的conf文件保存着

[root@localhost redis-7.2]# mkdir /myredis
[root@localhost redis-7.2]# cp redis.conf /myredis/redis7.conf

修改 /opt/redis-7.2/redis.conf 配置文件

修改前修改后
daemonize nodaemonize yes
protected-mode yesprotected-mode no
bind 127.0.0.1# bind 127.0.0.1
# requirepass foobaredrequirepass 自定义密码

根据配置文件启动redis服务

redis-server /myredis/redis7.conf

若报错:

WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add ‘vm.overcommit_memory = 1’ to /etc/sysctl.conf and then reboot or run the command ‘sysctl vm.overcommit_memory=1’ for this to take effect.

执行:

echo 1 > /proc/sys/vm/overcommit_memory

然后重新启动redis

2.2.4 连接redis客户端
redis-cli -a 密码 [-p 端口号]

redis-cli
auth 密码

端口号默认为6379

2.2.5 查看Redis版本
redis-server -v
2.2.6 检查
127.0.0.1:6379> ping
PONG

安装配置成功

2.2.7 关闭Redis客户端
quit
2.2.8 关闭Redis服务器

(1)客户端内部退出

SHUTDOWN

(2)客户端外部退出

​ 单实例关闭:

redis-cli -a 密码 shutdown

​ 多实例关闭(指定端口关闭):

redis-cli -p 端口号 shutdown
2.2.9 卸载

停止Redis服务

rm -rfv /usr/local/bin/redis-*

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

2.3 Docker 部署

修改前修改后
protected-mode yesprotected-mode no
bind 127.0.0.1bind 0.0.0.0
# requirepass foobaredrequirepass 自定义密码
logfile “”logfile “/var/log/redis.log”
docker run -d \
-p 6379:6379 \
--name redis \
--restart=always \
--privileged=true \
-v /docker/redis/conf/redis.conf:/etc/redis/redis.conf \
-v /docker/redis/data:/data \
-v /docker/redis/logs/redis.log:/var/log/redis.log \
redis redis-server \
/etc/redis/redis.conf

3. Redis数据类型

3.1 基础

key-value 键值对中,key是字符串类型,value可以是10种数据类型。

文档:https://redis.io/docs/latest/commands/

3.2 字符串String

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

3.3 列表List

简单的字符串列表,按照插入顺序排序,可以添加元素到列表的头部或者尾部。

底层是一个双端链表。

3.4 哈希表Hash

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

3.5 集合Set

是String类型的无序集合,集合成员是唯一的。集合对象的编码可以是intset或者hashtable。

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

3.6 有序集合ZSet

跟Set一样也是String类型的集合,集合成员是唯一的。

但是每个元素都会关联一个double类型的分数(score),分数是可以重复的,通过分数来为集合中的成员进行从小到大的排序。

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

3.7 地理空间GEO

主要用于储存地理位置信息(经纬度),并对存储的信息进行操作。

包括添加地理位置的坐标、获取地理位置的坐标、计算两个位置之间的距离、根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。

3.8 基数统计HyperLogLog

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

HyperLogLog花费很少的内存就可以计算很多很多个不同元素的基数。(网页点击量等)

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

3.9 位图bitmap

由0和1状态表现的二进制位的bit数组(打卡、签到等)

3.10 位域bitfield

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

3.11 流Stream

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

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

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


4. Redis 操作命令

4.1 键 key命令

4.1.1 常见命令
命令作用
keys *查看当前库所有的key
exists key判断某个key是否存在
type key查看key的类型
del key删除指定的key数据
unlink key非阻塞删除,仅将keys从keyspace中删除,真正的删除会在后续异步中操作
ttl key(Time To Leave)查看还有多少秒过期,-1:用不过期;-2:已经过期
expire key 秒钟为给定的key设置过期时间
move key dbindex [0-15]将当前数据库的key移动到给定的数据库db中
select dbindex切换数据库[0-15],默认为0
dbsize查看当前数据库key的数量
flushdb清空当前数据库
flushall清空全部库
4.1.2 同时设置/获取多个值
命令作用
mset key value [key value …]设置多个键值对
mget key [key …]获取多个值
msetnx key value [key value …]事务式设置多个键值对
4.1.3 获取指定区间范围内的值
命令作用
setrange key (offset) (str)在offset后面追加str
getrange key (beginIndex) (endIndex)相当于java的substring(int beginIndex,int endIndex),endIndex=-1时表示到最后索引
4.1.4 数值增减

一定要是数字,才可以加减

命令作用
incr key递增数字
incrby key (increment)增加指定的整数
decr key递减数字
decrby key (decrement)减少指定的整数
4.1.5 获取字符串长度和内容追加
命令作用
strlen key获取字符串长度
append key value追加value
4.1.6 分布式锁
命令
setnx key value
setex(set with expire) 键 秒值 / setnx(set if not exist)
4.1.7 getset
命令作用
getset key value先get后set

4.2 值 value命令

帮助命令:help @类型

4.2.1 字符串String
4.2.2 列表List

单key多value

命令作用
lpush / rpush / lrange beginIndex endIndex左插入 / 右插入 / 遍历列表
lpop / rpop左删除 / 右删除
lindex按照索引下标获得元素(从上到下)
llen获取列表中元素个数
lrem key N value1删除N个值为value1的元素(N=0时表示所有个)
ltrim key beginIndex endIndex截取指定范围的值后再赋值给key
rpoplpush 源列表 目的列表源列表右删除的值左插入到目标列表
lset key index value指定索引重新赋值
linsert key before / after 原有值 新值在第一个原有值前 / 后插入新值
4.2.3 哈希表Hash

K-V模式不变但是V时一个键值对

命令作用
hset / hget / hmset / hmget / hgetall / hdel——
hlen字段个数
hexists key是否存在某个字段
hkeys / hvals所有字段 / 所有字段值
hincrby / hincrbyfloat增加 / 减少数值
hsetnx——
4.2.4 集合Set

单key多value,且无重复

命令作用
sadd key member [member …]添加元素
smembers key遍历集合所有元素
sismember key member判断元素是否在集合中
srem key member [member …]删除元素
scard获取集合中的元素个数
srandmember key N从集合中随机展现设置的N个元素,元素不删除
spop key N从集合中随机删除N个元素
smove key1 key2 值(key1中的某个值)将key1中已经存在的某个值赋值个key2
集合运算作用
sdiff key [key …]计算差集(A - B)
sunion key [key …]计算并集(A∪B)
sinter key [key …]计算交集(A ∩B)
sintercard numkeys key [key …] [limit N]计算交集(A ∩B)。Redis7的新命令,不返回结果集,只返回结果的基数
4.2.5 有序集合ZSet
命令作用
zadd key score member [score member …]添加元素
zrange / zrevrange key start stop [withscores]按照score从小到大 / 从大到小的顺序返回从start到stop索引范围内的所有元素
zrangebyscore key [(]min [(]max [withscores] [limit offset count]获取指定分数范围内的元素,‘ ( ’ 表示取不到边界值
zscore key member获取元素的分数
zcard key获取集合中元素的数量
zrem key score1 value1删除元素
zincrby key increment member增加某个元素的分数
zcount key min max指定score范围内的元素个数
zmpop从列表弹出一个或多个元素,他们是成员分数对
zrank / zrevrank key value [value …]顺序 / 逆序获得下标值
4.2.6 地理空间GEO
命令作用
geoadd key 坐标 member [key 坐标 member …]添加数据
geopos key member返回经纬度
geohash key member返回坐标的geohash表示
geodist key member [m|km|ft|mi]两个位置之间的距离
georadius key 坐标 r [m|km|ft|mi] [withdist] [withcoord] [withhash] [count N]以半径r,坐标为中心,查找附近的members。withdist:将距离一并返回。withcoord:将目标位置坐标一并返回。withhash:返回编码后的值。count:限定返回的记录个数。
georadiusbymember key member r [m|km|ft|mi] [withdist] [withcoord] [withhash] [count N]以半径r,member为中心,查找附近的members。withdist:将距离一并返回。withcoord:将目标位置坐标一并返回。withhash:返回编码后的值。count:限定返回的记录个数。

若中文乱码,登录的时候:redis-cli --raw

4.2.7 基数统计HyperLogLog

需求:统计UV(独立访客,即客户端IP)、用户搜索关键词的数量、搜索词条个数

命令作用
pfadd key element [element …]添加指定元素到HyperLogLog
pfcount key [key …]返回给定的HyperLogLog的基数估计值
pfmerge destkey sourcekey [sourcekey …]将多个HyperLogLog合并为一个HyperLogLog
4.2.8 位图bitmap
命令作用
setbit key offset val给指定key值的offset位赋值val
getbit key offset获取指定key的offset位
strlen统计占多少字节
bitcount key [start end]返回指定key中[start,end]中为1的数量
bitop operation destkey key对不同的二进制存储数据进行位运算(operation=AND/OR/NOT/XOR)
4.2.9 位域bitfield(用的少)
4.2.10 流Stream

相当于Redis版本的消息中间件(MQ)

  • 队列相关指令
命令作用
xadd key * member [member …]添加消息到队列末尾。消息ID必须比上个消息ID大,默认用 * 表示自动生成ID。返回的是消息的ID。
xrange / xrevrange key start end [count N]获取消息列表,忽略删除的消息。start表示开始值,- 表示最小。end表示结束值,+ 表示最大。count表示最多获取多少个值。
xdel key 消息ID删除消息
xlen key消息个数
xtrim key [maxlen N] [minid id]截取stream。maxlen:允许的最大长度,对流进行修剪限制长度。minid:允许的最小id,从某个id值开始比该值小的会被抛弃
xread [count N] [blocking milliseconds] streams key 消息ID用于获取消息(阻塞/非阻塞),只会返回大于指定ID的消息。消息ID为$时表示当前stream已经存储的最大的ID作为最后一个ID。0-0表示从最小的ID开始获取stream中的消息
  • 消费组相关指令
命令作用
xgroup create streamkey key [$|0]创建消费者组。$表示从尾开始,0表示从头开始
xreadgroup group key consumer [count N] streams streamkey >消费组内的消费者consumer1从mystream消息队列中读取所有消息,但是不同消费组的消费者可以消费同一条消息。“>”表示从第一条尚未消费的消息开始读取。
xpending streamkey key / xpending streamkey key - + N consumer查询每个消费组内所有消费者已读取但是尚未确认的消息 / 查询指定消费者已经读取的消息
xack streamkey key 消息ID向消息队列确认消息处理已完成

5. Redis 持久化

5.1 RDB

—— Redis DataBase

  • 自动生成

创建文件夹:mkdir /myredis/dumpfiles

进入Redis配置文件 /opt/redis-7.2/redis.conf

修改 save 项为:save 秒数 修改次数

修改 dir 项为:dir /myredis/dumpfiles

修改 dbfilename 项为:dbfilename dump6379.rdb (指定 dumpRDB 文件名)

重启redis服务:①redis-cli -a 密码 shutdownredis-server /opt/redis-7.2/redis.conf

在redis客户端内获取config配置信息:config get 属性值

配置完后RDB内的数据会以文件存在磁盘中的 /myredis/dumpfiles 目录下

  • 手动生成

异步(非阻塞)

bgsave

5.2 AOF

Append Only File

以日志的形式来记录每个写操作(不记录读操作),只允许追加文件不能改写文件,redis 启动之初会读取该文件重新构建。

AOF:Redis6 vs Redis7

Redis6Redis7Redis7 优化
合并数据删除历史 AOF无需合并,降低 cpu/io 开销
记录增量写入、新建AOF增量文件、基本AOF无需刷新内存,更低的资源消耗,更少的性能影响

Redis → AOF 缓冲区 → AOF 文件

  • AOF 缓冲区 → AOF 文件 有三种写回策略:(参数 appendsync,默认为 everysec

    • Always:同步写回,每个写命令执行完立即将日志写回磁盘
    • everysec:每个写命令执行完,只把日志写回到 AOF 缓冲区,每隔一秒将内容写回磁盘。
    • no:操作系统控制的写回,每个写命令执行完,只把日志写回到 AOF 缓冲区,由操作系统决定何时写回磁盘
  • 随着写入 AOF 文件的增加为了避免文件膨胀,会根据规则进行命令的合并(AOF 重写),以此压缩文件。当文件的大小超过了所设置的峰值,Redis 就会自动启用 AOF 文件的内容压缩,只保留可以恢复数据的最小指令集。(也可以通过手动 bgrewriteaof 来重写)

    • auto-aof-rewrite-percentage 100:根据上次重写后的 aof 大小,判断当前 aof 是否增长一倍。
    • auto-aof-rewrite-min-size 64mb:重写时满足的文件大小。
    • 上述连个参数条件要同时满足才会触发重写

① 开启 aof:conf 文件中修改:

appendonly yes

② aof 文件保存路径和文件名:

—— Redis6

aof 文件保存的路径跟 rdb 文件的保存路径是一样的,都是通过 dir 参数配置的。

aof 文件名有且仅有一个:

appendfilename "appendonly.aof"

—— Redis7

appenddirname "dirname"

aof 文件保存的路径为 dir 配置目录下的子目录 appenddirname 目录,即:(dir)/(appenddirname)/xxx.aof

Multi Part AOF 设计:

appendfilename "appendonly.aof"

但是 appendonly.aof 被分为了三个文件:appendonly.aof.1.base.rdb、appendonly.aof.1.incr.aof、appendonly.aof.manifest

  • appendonly.aof.1.base.rdb:基础AOF,一般由子进程通过重写产生,该文件最多一个
  • appendonly.aof.1.incr.aof:增量AOF,会在AOFRW开始执行时被创建,该文件可能存在多个
  • appendonly.aof.manifest:历史AOF,由 base.aof 和 incr.aof 变化而来。

异常恢复:(在 appendonly.aof.1.incr.aof 出现写入error)

redis-check-aof --fix [appendonly.aof.1.incr.aof路径]

5.3 RDB + AOF 混合持久化

在同时开启 rdb 和 aof 持久化时,重启时只会加载 aof。

加载过程:先判断是否存在 aof 有则直接加载、启动,若没有 aof,则判断是否存在 rdb,然后加载、启动。

# 开启混合方式
aof-use-rdb-preamble yes

rdb 做全量持久化,aof 做增量持久化。

5.4 纯缓存模式

同时关闭 aof 和 rdb:

# 禁用 rdb
save ""
# 禁用 aof
appendonly no

禁用模式下,任然可以使用 save / bgsave 来生成 rdb 文件,用 bgrewriteaof 生成 aof 文件。


6. Redis 事务

6.1 数据库事务 vs Redis事务

Redis事务:① 单独的隔离操作 ② 没有隔离级别的概念 ③ 不保证原子性 ④ 排他性

  • Redis 的事务仅仅保证事务里的操作会被连续独占的执行,redis 命令的执行是单线程架构的,在执行完事务内所有的命令前是不可能再去同时执行其他客户端的请求的
  • 因为在事务提交前任何指令都不会被实际执行,也就不存在 “事务里的查询要看到事务里的更新,在事务外查询不能看到” 的问题了
  • Redis 的事务不保证原子性,也就是不保证所有指令同时成功或者同时失败,只有决定是否开始执行全部指令的能力,没有执行到一半回滚的能力
  • Redis 会保证一个事务内的命令依次执行,不会被其他命令插入
multi
..
cmd1
cmd2
..
exec

6.2 Redis 事务命令

命令描述
DISCARD取消事务
EXEC执行所有事务
MULTI标记一个事务块的开始
UNWATCH取消 watch 命令对所有 key 的监视
WATCH key [key …]监视一个(或多个) key,如果事务执行之前这个 key 会被其他命令改动,
那么事务就会被打断
case1:正常执行(multi … exec)
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 k3 v3
QUEUED
127.0.0.1:6379(TX)> incr count
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK
4) (integer) 2
case2:放弃事务(multi … discard)
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 vv1
QUEUED
127.0.0.1:6379(TX)> set k2 vv2
QUEUED
127.0.0.1:6379(TX)> incr count
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get count
"2"
case3:全体连坐(multi . [error] . exec)
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 vv
QUEUED
127.0.0.1:6379(TX)> set k2			# 语法错误
(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> get k1
"v1"
case4:冤头债主(或 multi … exec [error])
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 vv1
QUEUED
127.0.0.1:6379(TX)> incr str			# str的值为字符类型
QUEUED
127.0.0.1:6379(TX)> set k2 vv2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> get k1
"vv1"
127.0.0.1:6379> get k2
"vv2"
127.0.0.1:6379> get str
"string"
case5:watch 监控

Client 1:

127.0.0.1:6379> set balance 100
OK
127.0.0.1:6379> watch balance		 # 1. 开启监视
OK
127.0.0.1:6379> multi				 # 2. 开启事务
OK
127.0.0.1:6379(TX)> set balance 110		# 4. 修改
QUEUED
127.0.0.1:6379(TX)> exec		   	 # 5. 提交事务
(nil)							   # 6. 事务错误
127.0.0.1:6379> get balance
"200"

Client 2:

127.0.0.1:6379> set balance 200		 # 3. 修改监视的对象
OK
127.0.0.1:6379> get balance
"200"

7. Redis 管道(pipe lining)

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

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

  1. 客户端到服务器端发送命令分为四步:发送命令、命令排队、命令执行、返回结果,并监听 socket 返回,通常以阻塞模式等待服务器响应。
  2. 服务端处理命令,并将结果返回给客户端。

解决方式:管道可以一次性发送多条命令给服务端,服务端依次处理完毕后,**通过一条响应一次性将结果返回,通过减少客户端与 Redis 的通信次数来实现降低往返延时时间。**即批处理命令的变种优化措施。

例子:有一个 cmd.txt 文件,存储了多条命令语句,使用管道批处理:

cat cmd.txt | redis-cli -a xxxx --pipe

7.1 管道对比原生批命令

  • 原生批量命令是原子性的,管道是非原子性的
  • 原生批量命令一次只能执行一种命令,管道支持批量执行不同的命令
  • 原生批量命令是服务端实现的,而管道需要服务端和客户端一同实现

7.2 管道对比事务

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

7.3 管道注意事项

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

8. Redis 发布订阅

不推荐使用,交给 MQ、kafka、RabbitMQ 就可以了。


9. Redis 复制机制 *

就是主从复制,master 以写为主,slave 以读为主。当 master 数据变化的时候,自动将新的数据异步同步到其他 slave 数据库。

9.1 主从复制的作用

  • 读写分离

  • 容灾恢复

  • 数据备份

  • 水平扩容支撑高并发

9.2 配置方式

“配从,不配主”

① 权限细节

slave 要配置 masterauth 来设置校验密码,否则的话 master 会拒绝 slave 的访问请求。

masterauth <master-password>

② 主要操作命令

  • info replication:查看复制节点的主从关系和配置信息
  • replicaof 主库ip 主库端口:一般写入进 redis.conf 配置文件内
  • slaveof 主库ip 主库端口:每次与 master 断开后,都需要重新连接,除非配置进 redis.conf。在运行期间修改 slave 节点的信息,如果该数据库已经是某个主数据库的从库,那么会停止和原主数据库的同步关系转而和新的主数据库同步。
  • slaveof no one:使当前数据库停止与其他数据库的同步,转成主数据库

9.3 配置流程

一主二从样例:

三大命令:主从复制、改换门庭、自立为王

  • replicaof 主库ip 主库端口(配从不配主)
  • slaveof 新主库ip 新主库端口
  • slaveof no one

① 文件控制 redis 配置(永久生效):

配置
开启 daemonize yes (若是 docker 部署则为 no)
注释 bind 127.0.0.1
protected-mode no
指定端口(默认)
指定工作目录 dir
pid 文件(默认)
log 文件名
requirepass
dump.rdb 名字
aof(可不开启)
(从机需要配置)从机访问主机的同行密码:masterauth(必须)

② 手动命令控制 redis 配置(临时生效,shutdown后重置):

  • slaveof 主库ip 主库端口

9.4 实战案例

  • 一主二从

    • 从库只能读取不能写操作
    • 从库首次启动会先同步主库所有数据再跟随
    • 主库 shutdown 后,从机不动,原地待命,从库数据可以继续使用
  • 薪火相传(上一个 slave 可以是下一个 slave 的 master,可以减轻主 master 的写压力)

    • 中途变更转向会清除之前的数据,重新建立拷贝最新的
  • 反客为主:slaveof no one

9.5 复制原理和工作流程

① slave 启动成功连接到 master 后会发送一个 sync 命令。slave 首次连接 master,一次完全同步(全量复制)将被自动执行,slave 原有的数据会被 master 数据覆盖清除

② master 节点收到 sync 命令后会开始在后台保存快照(RDB 持久化,主从复制时会触发 RDB),同时收集所有接收到的用于修改数据集命令缓存起来,master 节点执行完 RDB 持久化完后,master 将 RDB 快照文件和所有缓存的命令发送到所有 slave,已完成一次性完全同步。而 slave 服务在接收到数据库文件数据后,将其存盘并加载到内存中,从而完成复制初始化。

③ master 发出 PING 包的周期,默认是 10 秒。

④ master 继续将新的所有收集到的修改命令自动依次传给 slave,完成同步。

⑤ master 会检查 backlog 里面的 offset,master 和 slave 都会保存一个复制的 offset 还有一个 masterId,offset 是保存在 backlog 中的。master 只会把已经复制的 offset 后面的数据复制给 slave,类似于断点续传。

9.6 复制的缺点

  • 复制延时,信号衰弱
  • master 挂了怎么办?
    • 默认情况下,不会在 slave 节点中重新选一个 master
    • 这时候需要一个无人值守,选择新的 master —— 哨兵

10. Redis 哨兵 *

10.1 哨兵是什么

哨兵用来巡查后台 master 主机是否故障,如果故障了根据投票数自动将某一个从库转换为新主库,继续对外服务。

  • 监控 redis 运行状态,包括 master 和 slave
  • 当 master 宕机,能自动将 slave 切换成新的 master

10.2 哨兵的作用

  • 主从监控:控制主从 redis 库运行是否正常
  • 消息通知:哨兵可以将故障转移的结果发送给客户端
  • 故障转移:如果 master 异常,则会进行主从转换,将其中一个 slave 作为新的 master
  • 配置中心:客户端通过连接哨兵来获得当前 redis 服务的主节点地址

10.3 实战

1主2从3哨兵模式(三哨兵:自动监控和维护集群,不存放数据,只是吹哨人;一主二从:用于数据读取和存放)

流程
/myredis 目录下新建或者拷贝 sentinel.conf 文件(名字不能错)
配置 sentinel.conf 文件的内容
redis-sentinel sentinel.conf 启动哨兵

sentinel.conf 内容:

配置描述
bind——
daemonize——
protected-mode——
port(26379)
logfile——
pidfile——
dir——
sentinel monitor 主库名 主库ip 端口 法定投票数设置要监控的 master 服务器,法定投票数(quorum)
表示最少有几个哨兵认可客观下线,同意故障迁移的投票数
sentinel auth-pass 主库名 主库密码master 设置了密码,连接 master 服务的密码

Broken Pipe:表示对管的管道已经断开,往往发生在远端把这个读/写管道关闭了,你无法对这个管道进行读写操作。

在运行期间,主从库的文件内容会被 sentinel 动态的修改

master-slave 切换后,master 的 redis.conf、slave 的 redis.conf 以及 sentinel 的 sentinel.conf 的内容都会发生改变,即 master 的 redis.conf 会多一行的 slaveof 配置,sentinel.conf 的监视目标会随之调换。

一个哨兵可以同时监控多个主从库架构

10.4 哨兵运行流程和原理

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

① 三个哨兵监控一主二从,正常运行

② SDown 主观下线:是单个 sentinel 自己主观检测到的 master 的状态,从 sentinel 的角度来看,如果发送了 PING 包后,在一定时间内没有收到合法的回复,就达到了 SDown 的条件。配置文件中 down-after-milliseconds 设置了判断主观下线的时间长度(默认 30s)。

③ ODown 客观下线:ODown 需要一定数量的 sentinel,多个哨兵达成一致意见才能认为 master 客观上已经宕机。

④ 选举出领导者哨兵:让 leader 组织新 master 的选举,并让 leader 来根据票数指定新的 master。当主节点配判断客观下线后,各个哨兵节点会进行协商,先选举出一个 leader,并由该 leader 进行 failover(故障迁移)。

如何选举 leader?

**Raft 算法 * ** :基本思想是先到先得

在这里插入图片描述

⑤ 由 leader 开始推动故障切换流程并选出一个新的 master:

  • ​ 先将某个 slave 选中为新 master:选出新 master 的规则,要在 slave 节点健康的前提下(权限尽量高、复制数据的偏移量尽量大、RunId 尽量小)。

    • 优先级:redis.conf 文件中,优先级参数 slave-priority 或者 replica-priority 最高的从节点(参数值越小优先级越高)
    • 复制偏移位置 offset 最大的从节点
    • 最小 Run Id 的从节点
  • 根据新的 master 重新构建主从:先执行 slaveof no one 让选出来的节点成为新的主节点,并通过 slaveof 命令让其他节点成为其从节点

    • sentinel leader 会先对选举出来的新 master 执行 slaveof no one 操作,将其提升为 master 节点
    • sentinel leader 向其他 slave 发送命令,让剩余的 slave 成为新的 master 节点的 slave
  • 老 master 重新上线时要认新的 master

    • 将之前已经下线的老 master 设置为新选出的 master 的从节点,当老 master 重新上线后,它会成为新 master 的 slave
    • sentinel leader 会让原来的 master 降级为 slave 并恢复工作

上述的 failover 的操作均由 sentinel 独自完成,完全无需人工干预

10.5 使用建议

哨兵节点数量应为多个且为奇数,哨兵本身应该集群,保证高可用

各个哨兵节点的硬件配置应当一致

如果哨兵节点要部署在 Docker 容器里,尤其要注意端口的正确映射

哨兵集群+主从复制,并不能保证数据零丢失 —— 使用 Redis 集群


11. Redis 集群 *

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

在这里插入图片描述

  • **Redis 集群可以支持多个 master。**每个 master 又可以挂载多个 slave
  • 由于 cluster 自带 sentinel 的故障转移机制,内置了高可用的支持,无需再去使用哨兵功能
  • 客户端与 Redis 的节点连接,不再需要连接集群中的所有节点,只需要任意连接集群中的一个可用节点即可
  • 槽位 slot 负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽和数据之间的关系

11.1 集群算法-分片-槽位slot

Redis 集群的数据分片:

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

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

在这里插入图片描述

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

如何找到给定 key 的分片?

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

分片+哈希槽的优势:方便缩容扩容和数据分派查找,这种结构容易添加和删除节点。

11.2 slot 槽位映射算法 *

① 哈希取余分区:hash(key) % N

优点:简单粗暴,直接有效,在服务器个数固定不变时没有问题。

缺点:扩容缩容很麻烦,但是需要弹性扩容或故障停机的情况下,原来的取余公式就会发生变化。

② 一致性哈希算法分区

为了解决分布式缓存变动和映射问题,某个机器宕机了,分母数量变化了,自然取余就不行了。

三个步骤:

  • 算法构建一致性哈希环:对2^32取模
    • 有个哈希函数产生hash值,这个算法的所有可能的哈希值会构成一个全量集,这个集合可以构成一个hash空间[0~2^32-1],这是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连,让他在逻辑上形成一个环形空间。
  • Redis 服务器ip节点映射:将集群中各个 ip 节点映射到环的某一个位置上。
    • 将各个服务器使用哈希计算,这样每台机器就能确定其在哈希环上的位置。
  • key 落到服务器的落键规则:
    • 当需要存储kv键值对时,首先计算 key 的哈希值并取模确定此数据在环上的位置,从此位置顺时针遍历,第一台遇到的服务器就是其应该定位到的服务器,并将键值对存储到该节点上。

在这里插入图片描述

优点:一致性哈希算法具有容错性和扩张性

缺点:一致性哈希算法的数据倾斜问题

③ 哈希槽分区

哈希槽实质就是一个数组,数组[0~214-1]形成了哈希槽的空间(214-1 = 16384)

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

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

CRC16 算法产生的hash值有16bit,但是只用了14bit。

  • 如果槽位为65536,发送心跳信息的信息头达到8k,发送的心跳包过于庞大。槽位为16384时,只有2k。由于Redis节点需要一定数量的PING消息作为心跳包,如果这个PING消息的信息头太大了,浪费带宽。
  • Redis集群主节点数量基本不可能超过1000。集群节点越多,心跳包的消息体内携带的数据越多。如果超过1000个,也会导致网络拥堵。redis作者也不建议超过1000个节点。
  • 槽位小、节点少的情况下,压缩比高,容易传输。Redis主节点的配置信息中它所负责的哈希槽是通过一张bitmap的形似来保存的,在传输过程中会对bitmap进行压缩,但是如果bitmap的填充率(slots/N)很高的话,bitmap的压缩率就会低

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

11.3 集群环境搭建

① 三主三从 Redis 集群配置

配置
bind、daemonize、protected-mode、port、logfile、pidfile、dir、dbfilename、appendonly、appendfilename、requirepass、masterauth
cluster-enabled yes
cluster-config-file node-6381.conf
cluster-node-timeout 5000

② 构建主从关系:

redis-cli -a 密码 \
--cluster create \
--cluster-replicas 1 主节点1的ip:端口 从节点1的ip:端口 [主节点2的ip:端口 从节点2的ip:端口 ..]
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.56.45:6381 to 192.168.56.44:6379
Adding replica 192.168.56.46:6381 to 192.168.56.45:6379
Adding replica 192.168.56.44:6381 to 192.168.56.46:6379
M: 4454b8d07855a5880abaf4ed997957b5335199f1 192.168.56.44:6379
   slots:[0-5460] (5461 slots) master
S: b5642fe4f243ed20193092413c97e34e1aa65bea 192.168.56.44:6381
   replicates bbaf98c42d62a767c3590c75c88abe422904a04a
M: 55ed7116839daaf7ae16c112d11607fa9b13f896 192.168.56.45:6379
   slots:[5461-10922] (5462 slots) master
S: d7a575b4fc912d0ade99a7ebeb6ba52a1cded0ed 192.168.56.45:6381
   replicates 4454b8d07855a5880abaf4ed997957b5335199f1
M: bbaf98c42d62a767c3590c75c88abe422904a04a 192.168.56.46:6379
   slots:[10923-16383] (5461 slots) master
S: d8fae69cb565da6723c357ef54d90f89fe23ad6f 192.168.56.46:6381
   replicates 55ed7116839daaf7ae16c112d11607fa9b13f896
Can I set the above configuration? (type 'yes' to accept):

查看集群状态:

  • info replication
  • cluster nodes
  • cluster info

③ 一定要注意槽位的区间,需要路由到位!

防止路由失效,加上参数 -c

redis-cli -a 密码 -p 端口 -c

查看某个key属于的对应槽位:cluster keyslot

④ 主从容错切换迁移

当 master 宕机,其 slave 会成为新的 master

集群不保证数据一致性100%。一定会有数据丢失的情况

手动迁移故障 or 节点从属调整如何处理?

若要让原先的 master 继续当 master,可以在重启之后执行 cluster failover

⑤ 主从扩容

  • 新增 master 节点实例:
redis-cli -a 密码 --cluster add-node 新M的ip:端口 原有集群的ip:端口

查看集群情况:

redis-cli -a 密码 --cluster check 集群ip:端口
  • 重新分派槽位:reshard
redis-cli -a 密码 --cluster reshard 集群ip:端口
  • 分完槽位后给 master 添加 slave
redis-cli -a 密码 --cluster add-node 新S的ip:端口 新M的ip:端口 --cluster-slave --cluster-master-id 新M的节点ID

⑥ 主从缩容

  • 清除 slave 节点

    ​ 获取slave节点ID

redis-cli -a 密码 --cluster check 集群ip 端口

​ 删除节点:

redis-cli -a 密码 --cluster del-node S的ip:端口 节点ID
  • 清出来的槽号重新分配
redis-cli -a 密码 --cluster reshard 集群ip:端口
Source node #1:	要删除的M节点的ID
Source node #1:	done
  • 删除 slave 对应的 master
redis-cli -a 密码 --cluster del-node S的ip:端口 节点ID
  • 恢复三主三从

11.4 小结

集群常用操作命令和CRC16算法分析

  • 不在同一个slot槽位下的多键操作支持不好,用通识占位符
    • 不在同一个槽位下的键值无法使用 mset、mget 等多键操作,可以通过 { } 来定义同一个组的概念,使 key 中 { } 内相同内容的键值对放到一个 slot 槽位去

127.0.0.1:6379> mget k1 k2 k3
(error) CROSSSLOT Keys in request don’t hash to the same slot

192.168.56.44:6379> mset kk1{z} vv1 kk2{z} vv2 kk3{z} vv3
-> Redirected to slot [8157] located at 192.168.56.45:6379
OK

192.168.56.45:6379> mget kk1
-> Redirected to slot [6712] located at 192.168.56.44:6379
(nil)

192.168.56.44:6379> mget kk1{z} kk2{z} kk3{z}
-> Redirected to slot [8157] located at 192.168.56.45:6379
1.“vv1”
2.“vv2”
3.“vv3”

  • Redis集群有16384个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放在哪个槽。集群的每个节点负责一部分哈希槽
  • 常用命令:
    • 集群是否完整才能对外提供服务:cluster-require-full-coverage(默认为yes,即要求集群的完整性;修改为no时,集群不完整也会对外服务)
    • cluster countkeysinslot 槽位数字编号:该槽位是否被占用(1-占用,0-没占用)
    • cluster keyslot 键名称:键名称对应的哈希槽

12. Springboot 集成 Redis *

12.1 概述 (Jedis、Lettuce 和 RedisTemplate)

12.2 Jedis (第一代)

<!-- 1.jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.3.1</version>
</dependency>
@Test
void contextLoads() {
    Jedis jedis = new Jedis("192.168.56.44",6379);
    jedis.auth("xxxxx");
    System.out.println(jedis.ping());
}

12.3 **Lettuce **(第二代)

<!-- 2.Lettuce -->
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.2.3.RELEASE</version>
</dependency>
@Test
void contextLoads() {
    // 使用构造器链式编程来构建 RedisURI
    RedisURI build = RedisURI.Builder
        .redis("192.168.56.44")
        .withPort(6379)
        .withAuthentication("default", "yantianyu")
        .build();
    // 创建连接客户端
    RedisClient redisClient = RedisClient.create(build);
    StatefulRedisConnection<String, String> conn = redisClient.connect();
    // 通过 conn 创建操作命令
    RedisCommands<String, String> commands = conn.sync();
    // TODO 业务逻辑
    System.out.println(commands.ping());
    // 释放资源
    conn.close();
    redisClient.shutdown();
}

12.4 *RedisTemplate (推荐

  • 连接单机

    pom.xml:

<!-- Springboot 与 Redis 整合依赖 -->
<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>3.0.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>3.0.0</version>
</dependency>

application.yml:

spring:
  # swagger
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  # redis 单机
  data:
    redis:
      database: 0
      host: 127.0.0.1
      port: 6379
      password: xxxx
      lettuce:
        pool:
          max-active: 8
          max-wait: -1ms
          max-idle: 8
          min-idle: 0

当我们要在 Redis 存储中文字符:--raw

redis-cli -a 密码 -p 端口 --raw

RedisConfig.java:

@Configuration
public class RedisConfig {

    /**
     * Redis 配置
     * @param lettuceConnectionFactory  内置 lettuce 工厂
     * @return redisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        // 设置 key 序列化规则 string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 使用 GenericFastJsonRedisSerializer 替换 value 默认序列化规则
        redisTemplate.setValueSerializer(new GenericFastJsonRedisSerializer());
        // 设置hashKey和hashValue的序列化规则
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericFastJsonRedisSerializer());
        // 设置支持事物
        redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
  • 连接集群

​ application.yml:

spring:
  # swagger
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  data:
    # redis 集群
    redis:
      password: yantianyu
      cluster:
        max-redirects: 3
        nodes: 192.168.56.44:6379,192.168.56.44:6381,192.168.56.45:6379,192.168.56.45:6381,192.168.56.46:6379,192.168.56.46:6381
      lettuce:
        pool:
          max-wait: -1ms
          max-active: 8
          max-idle: 8
          min-idle: 0

一些问题:

  1. 在 Springboot 整合 Redis 集群的时候,若有一个 master 宕机,很定会由它的 slave 顶替,Redis 集群不会有问题,但是在 spring 框架中的连接会有问题。

由于 Springboot 客户端不会动态感知 RedisCluster 的最新集群信息,导致一旦有机器宕机 Springboot 客户端 就出现问题。Springboot2.x 版本,Redis 默认的连接池采用 Lettuce,当 Redis 集群节点发生变化后,Lettuce 默认是不会刷新节点拓扑的。

解决方案:

  • 排除 Lettuce 使用 Jedis(不推荐)
  • 重写连接工厂实例(特别不推荐)
  • 刷新节点集群拓扑动态感知(√)
# 开启集群拓扑动态感知
spring.data.redis.lettuce.cluster.refresh.adaptive=true
spring.data.redis.lettuce.cluster.refresh.period: 2000
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值