Redis篇:Redis高级之主从、集群、哨兵、缓存优化


详情见:http://www.liuqingzheng.top/db/Redis系列/08-Redis系列之-Redis-Cluster/

一、Redis主从复制

1.1 单实例存在的问题

机器故障;容量瓶颈;QPS瓶颈

1.2 什么是主从复制

架构:一主一从,一主多从

作用:
    做读写分离
    做数据副本
    扩展数据性能

注意:
    一个maskter可以有多个slave
    一个slave只能有一个master
    数据流向是单向的,从master到slave

1.3 主从复制原理

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 辅助配置(主从数据一致性配置)

min-slaves-to-write 1
min-slaves-max-lag 3
# 那么在从服务器的数量少于1个,或者三个从服务器的延迟(lag)值都大于或等于3秒时,主服务器将拒绝执行写命令

1.6 配置方式

1.6.1 方式1 slave命令

6380是从,6379是主
在6380上执行(去从库配置,配置主库)

slaveof 127.0.0.1 6379 #异步
slaveof no one #取消复制,不会把之前的数据清除

1.6.2 方式2 配置文件

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 主从复制,主节点发生故障,需要做故障转移,可以手动转移:让其中一个slave变成master  (哨兵来做:高可用)
2 主从复制,只能主写数据,所以写能力和存储能力有限(集群来做)

2.2 原理:一个sentinel就是一个进程

1 多个sentinel发现并确认master有问题
2 选举触一个sentinel作为领导
3 选取一个slave作为新的master
4 通知其余slave成为新的master的slave
5 通知客户端主从变化
6 等待老的master复活成为新master的slave

image

2.3 安装配置

2.3.1 配置文件信息

# 配置开启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 搭一个一主两从
    # 创建三个配置文件:
    # 第一个是主配置文件
    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操作哨兵(客户端链接)

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 并发量:单机redis qps为10w/s,但是我们可能需要百万级别的并发量
2 数据量:机器内存16g--256g,如果存500g数据呢?

3.2 解决

# 解决:加机器,分布式
redis cluster 在2015年的 3.0 版本加入了,满足分布式的需求

3.3 数据分布(分布式数据库)

3.3.1 存在问题

假设全量的数据非常大,500g,单机已经无法满足,我们需要进行分区,分到若干个子集中

3.3.2 分区方式

分区方式 特点 产品
哈希分布 数据分散度高,建值分布于业务无关,无法顺序访问,支持批量操作 一致性哈希memcache,redis cluster,其他缓存产品
顺序分布 数据分散度易倾斜,建值业务相关,可顺序访问,支持批量操作 BigTable,HBase
- 顺序分区
    -原理: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 配置开启节点

# 搭建一个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,分配槽,指定主从

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脚本)

# 下载编译安装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 集群扩容

# 启动两台集群:一个主,一个从
-第一步启动两台机器
    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 集群缩容

# 第一步:下线迁槽(把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)

# 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 缓存更新策略

LRU/LFU/FIFO算法剔除:例如maxmemory-policy(到了最大内存,对应的应对策略)
    ​ LRU -Least Recently Used,没有被使用时间最长的
    ​ LFU -Least Frequenty User,一定时间段内使用次数最少的
    ​ FIFO -First In First Out  最早放的,先剔除

4.2 缓存穿透,击穿,雪崩

### 缓存穿透
# 描述:
    缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
# 解决方案:
    1 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
    2 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
    3 通过布隆过滤器实现


### 缓存击穿
# 描述:
    缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
# 解决方案:
    设置热点数据永远不过期。

 
### 缓存雪崩
#描述:
    缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
# 解决方案:
    1 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
    2 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
    3 设置热点数据永远不过期。
posted @ 2022-08-22 20:21  马氵寿  阅读(186)  评论(0编辑  收藏  举报