Redis篇:Redis高级之主从、集群、哨兵、缓存优化
详情见:http://www.liuqingzheng.top/db/Redis系列/08-Redis系列之-Redis-Cluster/
一、Redis主从复制
1.1 单实例存在的问题
复制代码
- 1
机器故障;容量瓶颈;QPS瓶颈
1.2 什么是主从复制
架构:一主一从,一主多从
作用:
做读写分离
做数据副本
扩展数据性能
注意:
一个maskter可以有多个slave
一个slave只能有一个master
数据流向是单向的,从master到slave
1.3 主从复制原理
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
1. 副本库通过slaveof 127.0.0.1 6379命令,连接主库,并发送SYNC给主库
2. 主库收到SYNC,会立即触发BGSAVE,后台保存RDB,发送给副本库
3. 副本库接收后会应用RDB快照
4. 主库会陆续将中间产生的新的操作,保存并发送给副本库
5. 到此,我们主复制集就正常工作了
6. 再此以后,主库只要发生新的操作,都会以命令传播的形式自动发送给副本库.
7. 所有复制相关信息,从info信息中都可以查到.即使重启任何节点,他的主从关系依然都在.
8. 如果发生主从关系断开时,从库数据没有任何损坏,在下次重连之后,从库发送PSYNC给主库
9. 主库只会将从库缺失部分的数据同步给从库应用,达到快速恢复主从的目的
1.4 主库是否要开启持久化
如果不开有可能,主库重启操作,造成所有主从数据丢失!
1.5 辅助配置(主从数据一致性配置)
复制代码
- 1
- 2
- 3
min-slaves-to-write 1
min-slaves-max-lag 3
# 那么在从服务器的数量少于1个,或者三个从服务器的延迟(lag)值都大于或等于3秒时,主服务器将拒绝执行写命令
1.6 配置方式
1.6.1 方式1 slave命令
复制代码
- 1
- 2
- 3
- 4
- 5
6380是从,6379是主
在6380上执行(去从库配置,配置主库)
slaveof 127.0.0.1 6379 #异步
slaveof no one #取消复制,不会把之前的数据清除
1.6.2 方式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
slaveof ip port # 配置从节点ip和端口
slave-read-only yes # 从节点只读,因为可读可写,数据会乱
mkdir -p redis1/conf redis1/data redis2/conf redis2/data redis3/conf redis3/data
vim redis.conf # 编辑redis配置信息
-----------------------------------------------
daemonize no
pidfile redis.pid
bind 0.0.0.0
protected-mode no
port 6379
timeout 0
logfile redis.log
dbfilename dump.rdb
dir /data
slaveof 10.0.0.101 6379 # 加入左边两行
slave-read-only yes
-----------------------------------------------
cp redis.conf /home/redis2/conf/
cp redis.conf /home/redis3/conf/
# 启动redis服务
docker run -p 6379:6379 --name redis_6379 -v /home/redis1/conf/redis.conf:/etc/redis/redis.conf -v /home/redis1/data:/data -d redis redis-server /etc/redis/redis.conf
docker run -p 6378:6379 --name redis_6378 -v /home/redis2/conf/redis.conf:/etc/redis/redis.conf -v /home/redis2/data:/data -d redis redis-server /etc/redis/redis.conf
docker run -p 6377:6379 --name redis_6377 -v /home/redis3/conf/redis.conf:/etc/redis/redis.conf -v /home/redis3/data:/data -d redis redis-server /etc/redis/redis.conf
info replication
二、哨兵
2.1 主从复制高可用
复制代码
- 1
- 2
- 3
# 主从复制存在的问题
1 主从复制,主节点发生故障,需要做故障转移,可以手动转移:让其中一个slave变成master (哨兵来做:高可用)
2 主从复制,只能主写数据,所以写能力和存储能力有限(集群来做)
2.2 原理:一个sentinel就是一个进程
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
1 多个sentinel发现并确认master有问题
2 选举触一个sentinel作为领导
3 选取一个slave作为新的master
4 通知其余slave成为新的master的slave
5 通知客户端主从变化
6 等待老的master复活成为新master的slave
2.3 安装配置
2.3.1 配置文件信息
复制代码
- 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
# 配置开启sentinel监控主节点
mkdir -p redis4/conf redis4/data redis5/conf redis5/data redis6/data redis6/conf
vi sentinel.conf
---------------------配置文件---------------------------------------
port 26379
daemonize yes
dir data
protected-mode no
bind 0.0.0.0
logfile "redis_sentinel.log"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
-------------------------------------------------------------------
# 配置文件解释
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
(1)sentinel monitor <master-name> <ip> <redis-port> <quorum>
# 告诉sentinel去监听地址为ip:port的一个master,这里的master-name可以自定义,quorum是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效
(2)sentinel auth-pass <master-name> <password>
# 设置连接master和slave时的密码,注意的是sentinel不能分别为master和slave设置不同的密码,因此master和slave的密码应该设置相同。
(3)sentinel down-after-milliseconds <master-name> <milliseconds>
# 这个配置项指定了需要多少失效时间,一个master才会被这个sentinel主观地认为是不可用的。 单位是毫秒,默认为30秒
(4)sentinel parallel-syncs <master-name> <numslaves>
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
(5)sentinel failover-timeout <master-name> <milliseconds>
'''
failover-timeout 可以用在以下这些方面:
1. 同一个sentinel对同一个master两次failover之间的间隔时间。
2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
3.当想要取消一个正在进行的failover所需要的时间。
4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了。
'''
2.3.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
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
1 搭一个一主两从
# 创建三个配置文件:
# 第一个是主配置文件
daemonize yes
pidfile /var/run/redis.pid
port 6379
dir "/opt/soft/redis/data"
logfile “6379.log”
# 第二个是从配置文件
daemonize yes
pidfile /var/run/redis2.pid
port 6378
dir "/opt/soft/redis/data2"
logfile “6378.log”
slaveof 127.0.0.1 6379
slave-read-only yes
# 第三个是从配置文件
daemonize yes
pidfile /var/run/redis3.pid
port 6377
dir "/opt/soft/redis/data3"
logfile “6377.log”
slaveof 127.0.0.1 6379
slave-read-only yes
# 把三个redis服务都启动起来
./src/redis-server redis_6379.conf
./src/redis-server redis_6378.conf
./src/redis-server redis_6377.conf
2 搭建哨兵
# sentinel.conf这个文件
# 把哨兵也当成一个redis服务器
创建三个配置文件分别叫sentinel_26379.conf sentinel_26378.conf sentinel_26377.conf
# 当前路径下创建 data1 data2 data3 个文件夹
#内容如下(需要修改端口,文件地址日志文件名字)
port 26379
daemonize yes
dir ./data3
protected-mode no
bind 0.0.0.0
logfile "redis_sentinel3.log"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
# 启动三个哨兵
./src/redis-sentinel sentinel_26379.conf
./src/redis-sentinel sentinel_26378.conf
./src/redis-sentinel sentinel_26377.conf
# 登陆哨兵
./src/redis-cli -p 26377
# 输入 info
# 查看哨兵的配置文件被修改了,自动生成的
# 主动停掉主redis 6379,哨兵会自动选择一个从库作为主库
redis-cli -p 6379
shutdown
# 强制批量停掉某些进程
pkill -9 redis-server
2.3.3 python操作哨兵(客户端链接)
复制代码
- 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
import redis
from redis.sentinel import Sentinel
# 连接哨兵服务器(主机名也可以用域名)
# 10.0.0.101:26379
sentinel = Sentinel([('127.0.0.1', 26379),
('127.0.0.1', 26380),
('127.0.0.1', 26381)
],
socket_timeout=5)
print(sentinel)
# 获取主服务器地址
master = sentinel.discover_master('mymaster')
print(master)
# 获取从服务器地址
slave = sentinel.discover_slaves('mymaster')
print(slave)
##### 读写分离
# 获取主服务器进行写入
master = sentinel.master_for('mymaster', socket_timeout=0.5)
w_ret = master.set('foo', 'bar')
slave = sentinel.slave_for('mymaster', socket_timeout=0.5)
r_ret = slave.get('foo')
print(r_ret)
三、Redis Cluser:集群
3.1 问题
复制代码
- 1
- 2
- 3
# 存在问题
1 并发量:单机redis qps为10w/s,但是我们可能需要百万级别的并发量
2 数据量:机器内存16g--256g,如果存500g数据呢?
3.2 解决
复制代码
- 1
- 2
# 解决:加机器,分布式
redis cluster 在2015年的 3.0 版本加入了,满足分布式的需求
3.3 数据分布(分布式数据库)
3.3.1 存在问题
假设全量的数据非常大,500g,单机已经无法满足,我们需要进行分区,分到若干个子集中
3.3.2 分区方式
分区方式 | 特点 | 产品 |
---|---|---|
哈希分布 | 数据分散度高,建值分布于业务无关,无法顺序访问,支持批量操作 | 一致性哈希memcache,redis cluster,其他缓存产品 |
顺序分布 | 数据分散度易倾斜,建值业务相关,可顺序访问,支持批量操作 | BigTable,HBase |
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 顺序分区
-原理:100个数据分到3个节点上 1--33第一个节点;34--66第二个节点;67--100第三个节点(很多关系型数据库使用此种方式)
- hash分区:原理:hash分区: 节点取余 ,假设3台机器, hash(key)%3,落到不同节点上
-节点取余分区
-一致性哈希分区
-虚拟槽分区
-redis使用虚拟槽分区,总共有16284个槽
-原理:5个节点,把16384个槽平均分配到每个节点,客户端会把数据发送给任意一个节点,通过CRC16对key进行哈希对16383进行取余,算出当前key属于哪部分槽,属于哪个节点,每个节点都会记录是不是负责这部分槽,如果是负责的,进行保存,如果槽不在自己范围内,redis cluster是共享消息的模式,它知道哪个节点负责哪些槽,返回结果,让客户端找对应的节点去存服务端管理节点,槽,关系
具体见:http://www.liuqingzheng.top/db/Redis系列/08-Redis系列之-Redis-Cluster/
3.4 原生安装流程
集群的概念流程:节点,meet,指派槽,复制,高可用
3.4.1 配置开启节点
复制代码
- 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
# 搭建一个6台机器,3个节点的集群,另外3台机器是从库,启动两台(一主一从),扩容成4个节点,缩容成3个节点
-准备6个配置文件
port ${port}
daemonize yes
dir "/opt/redis/redis/data/"
logfile "${port}.log"
# masterauth 集群搭建时,主的密码
cluster-enabled yes # 开启cluster
cluster-node-timeout 15000 # 故障转移,超时时间 15s
cluster-config-file nodes-${port}.conf # 给cluster节点增加一个自己的配置文件
cluster-require-full-coverage yes #只要集群中有一个故障了,整个就不对外提供服务了,这个实际不合理,假设有50个节点,一个节点故障了,所有不提供服务了,需要设置成no
# 快速生成其他配置6个排位置文件
sed 's/7000/7001/g' redis-7000.conf > redis-7001.conf
sed 's/7000/7002/g' redis-7000.conf > redis-7002.conf
sed 's/7000/7003/g' redis-7000.conf > redis-7003.conf
sed 's/7000/7004/g' redis-7000.conf > redis-7004.conf
sed 's/7000/7005/g' redis-7000.conf > redis-7005.conf
# 启动6台机器
./src/redis-server redis-7000.conf
./src/redis-server redis-7001.conf
./src/redis-server redis-7002.conf
./src/redis-server redis-7003.conf
./src/redis-server redis-7004.conf
./src/redis-server redis-7005.conf
ps -ef |grep redis
3.4.2 meet,分配槽,指定主从
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
# 3个命令
cluster nodes
cluster slots
cluster info
# 链接服务的
redic-cli -c # 集群模式链接,设置获取值,如果不在当前节点,会自动转过去,并完成数据操作
3.5 官方安装工具(ruby脚本)
复制代码
- 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
# 下载编译安装ruby
wget https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.8.tar.gz
tar -zxvf ruby-2.5.8.tar.gz
cd ruby
./configure -prefix=/usr/local/ruby
make && make install
cd /usr/local/ruby
cp bin/ruby /usr/local/bin # ruby类似于python3
cp bin/gem /usr/local/bin # gem类似于pip
ruby -v # 检查版本
# 安装rubygem redis
### 更换gem源
gem sources -l
# 移除https://rubygems.org源
gem sources --remove https://rubygems.org/
# 增加https://gems.ruby-china.com/源
gem sources -a https://gems.ruby-china.com/
# 查看
gem sources -l
## 安装gem redis
gem install redis -v 3.3.3
# 查看
gem list check redis gem
# 安装redis-trib.rb
cd /opt/soft/redis/src
./redis-trib.rb 弃用了,需要使用
# 1 表示给每个主节点配置一个从节点
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
yes
3.6 集群扩容
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
# 启动两台集群:一个主,一个从
-第一步启动两台机器
sed 's/7000/7006/g' redis-7000.conf > redis-7006.conf
sed 's/7000/7007/g' redis-7000.conf > redis-7007.conf
redis-server conf/redis-7006.conf
redis-server conf/redis-7007.conf
-这两台机器加入到机器中(add-node)
./src/redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
./src/redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7000
-让7007作为7006的从
./src/redis-cli -p 7007 cluster replicate 62b1982a2430731609a4393cb303fa47d9a5b930
-迁移槽:从每台机器均匀的移动一部分槽给新的机器
./src/redis-cli --cluster reshard 127.0.0.1:7000
# 希望迁移多少个槽:4096
# 希望那个id是接收的:7006的id
# 传入source id :all
# yes
3.7 集群缩容
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
# 第一步:下线迁槽(把7006的1366个槽迁移到7000上)
./src/redis-cli --cluster reshard --cluster-from 62b1982a2430731609a4393cb303fa47d9a5b930 --cluster-to 6d65ffac96847c2a1ebb7240a7e218ccc843c7eb --cluster-slots 1365 127.0.0.1:7000
yes
./src/redis-cli --cluster reshard --cluster-from 62b1982a2430731609a4393cb303fa47d9a5b930 --cluster-to 0c47e4ff6523169ac293cdb5afcf65de2f3b7465 --cluster-slots 1366 127.0.0.1:7003
yes
./src/redis-cli --cluster reshard --cluster-from 62b1982a2430731609a4393cb303fa47d9a5b930 --cluster-to fdd4532103143b59a6761973cf0eaeee94ab9820 --cluster-slots 1366 127.0.0.1:7002
yes
# 第二步:下线节点 忘记节点,关闭节点
redis-cli --cluster del-node 127.0.0.1:7000 b1daf008b8b9706230f31be3f462fc6bd469db50 # 先下从,再下主,因为先下主会触发故障转移
redis-cli --cluster del-node 127.0.0.1:7000 62b1982a2430731609a4393cb303fa47d9a5b930
# 第三步:关掉其中一个主,另一个从立马变成主顶上, 重启停止的主,发现变成了从
3.8 python操作集群(redis-py-cluster)
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
# rediscluster
# pip3 install redis-py-cluster
from rediscluster import RedisCluster
startup_nodes = [{"host":"127.0.0.1", "port": "7000"},{"host":"127.0.0.1", "port": "7001"},{"host":"127.0.0.1", "port": "7002"}]
# rc = RedisCluster(startup_nodes=startup_nodes,decode_responses=True)
rc = RedisCluster(startup_nodes=startup_nodes)
rc.set("foo", "bar")
print(rc.get("foo"))
四、缓存优化
4.1 缓存更新策略
复制代码
- 1
- 2
- 3
- 4
LRU/LFU/FIFO算法剔除:例如maxmemory-policy(到了最大内存,对应的应对策略)
LRU -Least Recently Used,没有被使用时间最长的
LFU -Least Frequenty User,一定时间段内使用次数最少的
FIFO -First In First Out 最早放的,先剔除
4.2 缓存穿透,击穿,雪崩
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
### 缓存穿透
# 描述:
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
# 解决方案:
1 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
2 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
3 通过布隆过滤器实现
### 缓存击穿
# 描述:
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
# 解决方案:
设置热点数据永远不过期。
### 缓存雪崩
#描述:
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
# 解决方案:
1 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
3 设置热点数据永远不过期。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步