05--Redis高级:持久化、主从复制、哨兵、集群、redis做缓存
1 redis持久化
# 持久化
redis的所有数据保存在内存中,对数据更新将异步的保存到硬盘上
# 实现方式
# 1.快照 (全量复制):某时某刻数据的一个完整备份
-mysql的 Dump
-redis的 RDB
# 2.写日志 (日志回放):任何操作记录日志,要恢复数据,只要把日志重新走一遍即可
-mysql的 Binlog
-Redis的 AOF
1.1 RDB配置
# 触发机制:主要三种方式
1.save命令 :客户端执行save命令--> redis服务端--> 同步创建RDB二进制文件,如果老的RDB存在,会替换老的
2.bgsave命令:客户端执行save命令--> redis服务端--> 异步创建RDB二进制文件,如果老的RDB存在,会替换老的
3.自动触发(配置文件) :save三条,只要符合一条 就会更新RDB # 内部采用bgsave
# save与 bgsave命令区别:
save:在当前进程执行生成RDB文件,当数据量特别大的时候,有可能导致redis阻塞
bgsave:以后台进程形式执行生成RDB文件,是异步操作 不会影响当前redis客户端访问 # 手动备份时 常用
# 最佳配置
save 900 1 # 如果900s中改变了1条数据,自动生成rdb
save 300 10 # 如果300s中改变了10条数据,自动生成rdb
save 60 10000 # 如果60s中改变了1w条数据,自动生成rdb
dbfilename dump-${port}.rdb # rdb文件的名字 以端口号作为文件名 默认为dump.rdb
dir /bigdiskpath # 快照(rdb文件) 保存路径放到一个大硬盘位置目录
stop-writes-on-bgsave-error yes # 如果bgsave出现错误,是否停止写入 默认为yes 出现错误停止
rdbcompression yes # 采用压缩格式 是
rdbchecksum yes # 是否对rdb文件进行校验 是
1.2 AOF配置
# AOF原理
客户端每写入一条命令,都记录一条日志,放到日志文件中,如果出现宕机,可以重新执行命令 将数据完全恢复
# AOF的三种策略 fsync调用模式
always (同步写回) : redis命令-> 写入命令刷新的缓冲区-> 每条命令fsync到硬盘(AOF文件)
everysec (每秒写回) : redis命令-> 写入命令刷新的缓冲区-> 每秒把缓冲区fsync到硬盘(AOF文件) # 默认
no (操作系统控制写回) : redis命令-> 写入命令刷新的缓冲区-> 由操作系统决定何时把缓冲区fsync到硬盘(AOF文件)
# 对比:
always : 数据不丢失 IO开销大 性能消耗高
everysec : 丢失1秒数据 性能适中 # 生产环境 常用
no : 数据丢失不可控 性能消耗低
# AOF重写
-前提:随着命令的逐步写入和并发量增大,AOF文件会越来越大,故通过AOF重写来解决该问题
-本质:本质就是把过期的、无用的、重复的、可以优化的命令,来优化AOF命令日志
-使用:
-在客户端主动输入命令:bgrewriteaof
-配置文件:
# AOF持久化配置最优方案
appendonly yes # 开启AOF持久化
appendfilename "appendonly.aof" # AOF文件保存的名字
appendfsync everysec # 采用第二种策略
dir ./data # AOF文件存放的路径
no-appendfsync-on-rewrite yes # 在AOF重写的时候,是否要不做AOF的append操作
# 因为AOF重写消耗性能和磁盘,与正常AOF写入磁盘有一定的冲突,这段期间的数据,允许丢失
2 主从复制
2.1 介绍与配置
# 产生背景
单个服务器库 易发生机器故障、容量瓶颈、QPS瓶颈(每秒查询率 每秒处理响应的查询次数)
# 主从复制 主库master 从库slave slave n. 奴隶,苦工
主库数据主动同步到从库
实现读写分离:主库写入数据,从库查询数据(不允许写)
# 原理
1.副本库通过slaveof 127.0.0.1 6379命令,连接主库,并发送SYNC(全量同步)给主库
2.主库收到SYNC,会立即触发BGSAVE,后台保存RDB,发送给副本库
3.副本库接收后会应用RDB快照
4.主库会陆续将中间产生的新的操作,保存并发送给副本库
5.到此,我们主复复制就正常工作了
6.再此以后,主库只要发生新的操作,都会以命令传播的形式自动发送给副本库
7.所有复制相关信息,从info信息中都可以查到.即使重启任何节点,主从关系依然都在
8.如果发生主从关系断开时,从库数据没有任何损坏,在下次重连之后,从库发送PSYNC(增量同步)给主库
9.主库只会将从库缺失部分的数据同步给从库应用,达到快速恢复主从的目的
# 强调
1.数据流向是单向的,只能由master流向slave
2.主库必须持久化 如果不开启 有可能主库重启操作,造成所有主从库数据丢失!
# 实现
一主一从、一主多从
# 如何实现:在从库中执行配置命令 或者 修改配置文件
方式一:在从库中执行
slaveof ip port # 配置要异步复制的 主节点ip 和端口
slaveof no one # 取消作为从库时,不会把之前的数据清除
方式二:配置文件 配在从库的配置文件中 # 常用
slaveof ip port
slave-read-only yes # 配置从节点只读 因为可读可写,数据会乱
# eg: 正常情况下 是两台机器 不是同一个机器两个进程(两个不同端口)
6380是从,6379是主
在6380上执行或配置 # 从库中 配置主库
# 方式一
slaveof 127.0.0.1 6379
# 方式二
slaveof 127.0.0.1 6379
slave-read-only yes
# 辅助配置(主从数据一致性配置)
min-slaves-to-write 1
min-slaves-max-lag 3
# 解读:
在从服务器的数量少于1个
或者三个从服务器的延迟(lag)值都大于或等于3秒时
主服务器将拒绝执行写命令
# 实际业务场景很少配置
实际生产中 并发量小的项目 基本都是单redis 偶尔会搭建redis主从复制
# 多个redis库 意味着部署多台机器 资源消耗严重
2.2 django缓存实现读写分离
# 第一步:settings中 配置多个redis
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://机器1的ip:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
"redis1": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://机器2的ip:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
# 第二步:使用
from django.core.cache import caches
caches['redis1'].set("name",'lqz') # 写
res=caches['default'].get('name') # 读
3 哨兵
3.1 介绍及配置
# 前提
1.主从复制存在的问题:
1.主从复制,主节点发生故障,需要做故障转移,可以手动转移:让其中一个slave变成master
2.主从复制,只能主写数据,所以写能力和存储能力有限
2.高可用 名词解释
通过设计减少系统不能提供服务的时间 俗讲:系统服务 能很长时间的 提供使用
# 哨兵 sentinel
sentinel是特殊的redis,可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能
# 作用
让redis的主从复制高可用 主要是解决了主从复制出现故障时需要人为干预的问题
# 主要功能
集群监控:多个sentinel负责监控Redis master和slave进程是否正常工作
消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
故障转移:如果master node挂掉了,会自动转移到slave node上
配置中心:如果故障转移发生了,通知client客户端新的master地址
# 安装配置
1.配置开启主从节点
2.配置开启sentinel监控主节点(sentinel是特殊的redis)
3.应该是多台机器
# 配置参数
sentinel monitor <master-name> <ip> <redis-port> <quorum>
# 配置sentinel去监听地址为ip:port的一个master
master-name # 主库名字 可以自定义
quorum # 指定一个数字,表明当有多少个sentinel认为一个master失效时,master才算真正失效
一般是 sentinel数量/2 + 1
monitor n.监控
sentinel auth-pass <master-name> <password>
# 设置连接master和slave时的密码
注意: sentinel不能分别为master和slave设置不同的密码
因此master和slave的密码应该设置相同
sentinel down-after-milliseconds <master-name> <milliseconds>
# 指定需要多少失效时间,master才会被这个sentinel主观地认为是不可用的。
单位是毫秒(ms),默认为30秒 30000ms
sentinel parallel-syncs <master-name> <numslaves>
# 指定在发生failover主备切换时,每次最多可以有多少个slave同时对新的master进行同步
通常将该值设为 1 表明故障切换时,每次只有一个slave来同步复制 处于了不能处理命令请求的状态
其他slave 依旧可以处理命令请求
数字越小,意味着完成failover所需的时间就越长
数字越大,意味着越多的slave因为replication复制状态而不可用
parallel n.平行
syncs v.同步
failover n.故障切换 失效备援
sentinel failover-timeout <master-name> <milliseconds>
# 指定故障转移failover 重试时间间隔,默认值为 180000ms 3min
failover-timeout 该选项会影响:
1.同一个sentinel对同一个master两次failover之间的间隔时间。
2.当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
3.取消一个正在进行的failover所需要的时间
4.当进行failover时,配置所有slaves指向新的master所需的最大时间。
不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了。
3.2 哨兵案例
# eg:
##### 1 搭一个一主两从
# 创建三个redis配置文件
# 第一个是主配置文件
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服务都启动起来
redis-server redis_6379.conf
redis-server redis_6378.conf
redis-server redis_6377.conf
##### 2 搭建哨兵
# 哨兵服务器就是sentinel.conf这个文件 也类似于一个redis服务器
# 创建三个哨兵配置文件
sentinel_26379.conf sentinel_26378.conf sentinel_26377.conf
# 前提:
当前路径下创建 data1 data2 data3 3个文件夹
需要修改 端口(port)、文件地址日志文件名字(logfile)、工作文件地址(dir)
# 配置文件内容如下
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
# 启动三个哨兵
redis-sentinel sentinel_26379.conf
redis-sentinel sentinel_26378.conf
redis-sentinel sentinel_26377.conf
# 登陆哨兵
redis-cli -p 26377
# 输入 info 可以查看当前master 地址
# 查看哨兵的配置文件被修改了,自动生成的
# 主动停掉主redis 6379,哨兵会自动选择一个从库作为主库
redis-cli -p 6379
shutdown
# 等待原来的主库启动,该主库会变成从库
3.3 客户端(python)连接
import redis
from redis.sentinel import Sentinel
# 连接哨兵服务器(主机名也可以用域名)
# 10.0.0.101:26379
sentinel = Sentinel([('10.0.0.101', 26379),
('10.0.0.101', 26378),
('10.0.0.101', 26377)],
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)
4 集群
4.1 介绍
# 产生背景
1 并发量:单机redis qps为10w/s,但是我们可能需要百万级别的并发量
2 数据量:机器内存16g--256g,如果存500g数据呢?
# 解决
搭建多台机器 采用 集群
# 理解:
分布式:不同的业务模块或者同一个业务模块分拆多个子业务,部署在不同的服务器上,解决高并发的问题
集群:同一个业务部署在多台机器上,提高系统可用性
分布式:多个人在一起作不同的事
集群:多个人在一起作同样的事
分布式集群:每一个环节不同的事 都可以配置多个人
# Redis Cluser 集群是3.0以后加的
3.0-5.0之间,采用ruby脚本实现 需要额外安装ruby
5.0以后,内置了命令搭建
# 注意
1.搭建集群 需要搭建主从复制 # 若没有搭建主从,某个节点故障,那么该节点的数据可能直接丢失了
2.不同节点 应当部署不同机器上
也可实现 三台机器 搭建6个节点 但每个机器上的从节点,应当复制另外机器的主节点
# 若每个机器上的从节点 复制本机的主节点,那么本机挂掉,依旧会丢失数据 备份数据库就没有起作用
3.Redis Cluster至少需要三个主节点才能工作
# eg: 集群 6个节点
3个主节点 --对应--> redis的16384个hash槽
3个从节点 作为主节点的备份
4.2 数据分布(分布式数据库)
# 了解 分布式数据库
是由一组数据组成的,这组数据分布在计算机网络的不同计算机上
网络中的每个节点具有独立处理的能力,可以执行局部应用
同时,每个节点也能通过网络通信子系统执行全局应用
# 对比传统数据库-分库分表的区别:
最大的区别,便体现在分布式事务上
单机数据库的事务是在一个节点上完成的,分布式数据库需要多个节点协调完成
# 个人理解:
就是把单一整体服务器拆分给多个数据库服务器节点进行处理,每一个节点都可以处理和存储数据,对外是整体暴露的
# 数据分布(分区)的原因
假设全量的数据非常大,500g,单机已经无法满足,我们需要进行分区存储,分到若干个子集中
# 数据分区方式 数据库都有这两种数据分区
1.顺序分区
原理:100个数据分到3个节点上 1--33第一个节点;34--66第二个节点;67--100第三个节点
# 很多关系型数据库使用此种方式
2.哈希分区
原理:hash分区方式之一:节点取余 ,假设3台机器, hash(key)%3,落到不同节点上
# 方式对比
顺序分布: 数据分散度易倾斜,建值业务相关,可顺序访问,支持批量操作
# 分散度倾斜指的是
比如 按照用户id存储数据 id前一百在一个分区 可能id先建立的用户比后面更活跃
可能会导致 某个分区读写次数 比其他分区 高很多
# eg: BigTable,HBase
哈希分布: 数据分散度高,建值分布于业务无关,无法顺序访问,支持批量操作
# eg: 一致性哈希memcache,redis cluster,其他缓存产品
# 哈希分区的方法 详见:https://www.liuqingzheng.top/db/Redis%E7%B3%BB%E5%88%97/08-Redis%E7%B3%BB%E5%88%97%E4%B9%8B-Redis-Cluster/
1.节点取余分区
2.一致性哈希分区
3.虚拟槽分区
预设虚拟槽: 每个槽映射一个数据子集,一般比节点数大
良好的哈希函数 # eg: CRC16
服务端管理 节点、槽、数据 # eg: redis cluster(槽的范围0–16383)
# 原理: 节点数自定义控制 redis有16384个hash槽 # hash槽 很专业的词
把16384个槽平均分配到5个节点,每个节点都会记录是不是负责这部分槽
客户端会把数据发送给任意一个节点,通过CRC16对数据的key进行哈希得到数据值 再对16383进行取余
计算出当前key属于哪个节点,属于哪部分槽
如果是负责该槽,则进行保存
如果槽不在负责范围内,则返回正确的节点结果,让客户端找对应的节点去存
# redis cluster是共享消息的模式,每个节点都知道哪个节点负责哪些槽
4.3 集群搭建
# Redis Cluster架构 cluster n.集群 slot n.槽
开启节点、节点握手(meet相互通信)、节点指派hash槽、主从复制(高可用)
# 实现形式
通过客户端 发送命令的形式创建
# 集群节点配置
cluster-enabled yes # 是否开启cluster
cluster-node-timeout 15000 # 故障转移超时时间 默认 15s 自动带有哨兵功能
cluster-config-file nodes-7000.conf # 指定cluster节点的配置文件名字
自动生成配置文件内容 人为不可修改
cluster-require-full-coverage yes # 是否 cluster运行必须覆盖全部槽 slot
# redis cluster需要16384个slot都正常的时候才能对外提供服务 # 一般 需要设置成no
若 某段槽 对应的主从节点 都挂掉后,那么整个cluster也不能工作
# 命令参数 replicas n.副本 reshard v. 重新切分
redis-cli --cluster help # 查看集群命令 参数
create host1:port1 ... hostN:portN # 指定集群的节点 创建集群
--cluster-replicas <arg> # 指定每个主节点的从节点个数
reshard host:port # 集群的任意一节点进行迁移槽时,重新分slots
--cluster-from <arg> # 需要从哪些源节点上迁移slot,传递的是节点的node id,
可从多个源节点完成迁移,以逗号隔开
也可 all,源节点为集群的所有节点
不传递该参数的话,则会在迁移过程中提示用户输入
--cluster-to <arg> # slot需要迁移的目的节点的node id,目的节点只能填写一个
不传递该参数的话,则会在迁移过程中提示用户输入
--cluster-slots <arg> # 需要迁移的slot数量
不传递该参数的话,则会在迁移过程中提示用户输入
add-node new_host:new_port host:port # 添加节点,把新节点加入到指定的集群 默认:添加为主节点
--cluster-slave # 添加新节点作为从节点 默认:随机一个主节点
--cluster-master-id <arg> # 给新节点指定主节点
del-node host:port node_id # 删除给定的一个节点,成功后关闭该节点服务
# eg: 集群 6个节点
3个主节点 --对应--> redis的16384个hash槽
3个从节点 作为主节点的备份
# 1.配置6个节点服务器的配置文件
cd redis/conf
vim redis-7000.conf
# 写入
port 7000
daemonize yes
dir "/opt/soft/redis/data/"
logfile "7000.log"
dbfilename "dump-7000.rdb"
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-require-full-coverage no
# 快速生成其他配置
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
# 2.启动6个节点
redis-server ./conf/redis-7000.conf
redis-server ./conf/redis-7001.conf
redis-server ./conf/redis-7002.conf
redis-server ./conf/redis-7003.conf
redis-server ./conf/redis-7004.conf
redis-server ./conf/redis-7005.conf
# 3.节点meet,节点指派槽,主从配置 3.0--5.0版本需原生操作 或 安装ruby脚本(跟下面搭建一样)
# 4.创建集群 指定每个主节点的 从库数量为1个 故:6个节点 分成 三对主从
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
# 5.查看集群节点和槽信息
客户端 连接任意节点
redis-cli -p 7000 CLUSTER NODES # 集群节点信息
redis-cli -p 7000 cluster slots # 查看槽的信息
4.4 集群伸缩
# 伸缩原理
加入节点、删除节点 槽和数据需要在节点之间的移动
4.4.1 集群扩容
# 作用:为它迁移槽和数据实现扩容 作为从节点负责故障转移
# 1 准备新节点 一主一从 两个
-集群模式
-配置和其他节点统一
-启动后是孤儿节点
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
# 查看新节点 孤立状态re
redis-cli -p 7006 cluster nodes
# 2 加入集群和配置主从
-添加到集群
-新的两个节点 建立主从关系
# 添加到集群
### 方式一:连接7000主节点 执行meet通信
redis-cli -p 7000 cluster meet 127.0.0.1 7006
redis-cli -p 7000 cluster meet 127.0.0.1 7007
### 方式二:命令行形式 添加新节点到集群中
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7000
# 把7007做为7006的从 replicate v.复制
redis-cli -p 7007 cluster replicate 7006的id
# 查看
redis-cli -p 7000 cluster nodes # 已经添加到集群中
# 3 迁移槽和数据
-槽迁移计划
-迁移数据
redis-cli --cluster reshard 127.0.0.1:7000
>>> 打印当前集群状态
命令交互:
希望迁移多少个槽 : 4096
希望那个id是接收的 : 7006的id # 接受槽的节点id
传入source id : all # 槽的来源 从哪些节点迁移出来 all表示每个节点平均分一点
是否执行迁移计划 : yes # 真正开始迁移数据
# 也可以直接将参数 写在命令中
redis-cli --cluster reshard 127.0.0.1:7000 --cluster-from all --cluster-to 7006的id --cluster-slots 4096
>>> yes
# 查看集群信息
redis-cli -p 7000 cluster nodes
redis-cli -p 7000 cluster slots
向集群某主节点 添加一个新从节点
# 其他:如果想给7000 某个主节点 再加一个从节点 怎么弄?
# 方式一: 手动
启动起7000,meet一下7008 再让7008复制7000
redis-cli -p 7000 cluster meet 127.0.0.1 7008
redis-cli -p 7008 cluster replicate 7000的id
# 方式二: cluster 命令
redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7000 --cluster-slave --cluster-master-id 7000的id
4.4.2 集群缩容
# 步骤
1.当前节点 迁移槽 到其他节点 # 只能迁移到主节点身上
2.从集群中 下线 当前节点
# 下线迁槽 (把7006的1366个槽迁移到7000上)
redis-cli --cluster reshard --cluster-from 7006的id --cluster-to 7000的id --cluster-slots 1366 127.0.0.1:7000
>>> yes
redis-cli --cluster reshard --cluster-from 7006的id --cluster-to 7001的id --cluster-slots 1366 127.0.0.1:7000
>>> yes
redis-cli --cluster reshard --cluster-from 7006的id --cluster-to 7002的id --cluster-slots 1365 127.0.0.1:7000
>>> yes
# 忘记节点,关闭节点
关闭顺序:先下从,再下主,因为先下主会触发故障转移
redis-cli --cluster del-node 127.0.0.1:7000 要下线的7007id
redis-cli --cluster del-node 127.0.0.1:7000 要下线的7006id
redis-cli --cluster del-node 127.0.0.1:7000 c2d0ed9ff7b08086d233c43ba8c3da22c5604853
# 故障转移
关掉其中一个主,另一个从立马变成主顶上
重启停止的主,发现变成了从
4.5 客户端连接
##### 命令行连接
redis-cli -c -p 7000 # -c表示集群模式 若不加-c 设值或取值时,该节点没有对应的槽,不能操作,但会返回正确的节点位置
set hello world # ok
cluster keyslot php # 查看某个key 所在的槽 9244
set php sb # 不命中 会自动跳转到7001上操作 不加-c,只会返回错误,不会去执行7001上保存
##### python连接 需要安装 redis-py-cluster
# 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"))
5 redis做缓存
https://www.liuqingzheng.top/db/Redis系列/09-Redis系列之-缓存的使用和优化/
5.1 缓存使用场景
1 降低后端负载:对高消耗的sql,join结果集/分组统计的结果做缓存
2 加速请求响应:利用redis优化io响应时间
3 大量写合并为批量写:如计数器先redis累加再批量写入db
5.2 缓存更新
# 缓存更新策略 前两种自带 第三种程序主动更新
# 1.内存溢出淘汰策略
即:超过maxmemory-policy(最大内存),新的数据放不进去了 此时需要淘汰一些数据
# LRU/LFU/FIFO算法剔除
LRU : Least Recently Used # 最长时间 没有被使用的 # 保证热点数据 常用
LFU : Least Frequently Used # 一定时间段内 使用次数最少的
FIFO : First In First Out # 先进先出
# 1.1 如何保证redis中数据是最热(最新)的: 配置LRU的剔除算法
-配置文件中:
maxmemory-policy: volatile-lru
volatile-lru # 对有过期时间的key采用LRU淘汰算法 volatile n.易丢失的
allkeys-lru # 对全部key采用LRU淘汰算法
还有其他内存溢出淘汰的参数 自行百度
# 1.2 LFU配置
-Redis4.0之后为maxmemory_policy淘汰策略添加了两个LFU模式
volatile-lfu # 对有过期时间的key采用LFU淘汰算法
allkeys-lfu # 对全部key采用LFU淘汰算法
-配置文件中:
maxmemory-policy: volatile-lfu
lfu-log-factor 10 # 默认为10 实际采用默认就可以
lfu-decay-time 1 # 默认为1
# counter并不是一个简单的线性计数器,而是用基于概率的对数计数器来实现
lfu-log-factor # counter的概率因子 数值越大 增长越慢
lfu-decay-time # counter的衰减因子 默认为1 也就是N分钟内没有访问,counter就要减N
# 2.超时剔除: eg: 设置过期时间(expire)
# 3.主动更新: 开发控制生命周期
即:常说的redis和mysql的双写一致性问题 就是保障redis和mysql数据同步
# 方案: 使用3和4
1.先更新数据库,再更新缓存 # 一般不用
# 缺点:
主要是怕两个并发的写操作导致脏数据
2.先删缓存,再更新数据库 # 也不是很好
# 缺点:
删完缓存,在存数据的过程中,新的请求又来了,又导致新的旧缓存 (且不是最新数据)
3.先更新数据库,再删缓存 # 推荐用
# 缺点:
在更新数据库时,此时的请求依旧是缓存的旧数据 但可理解
但只要再删掉缓存,数据就一致了
4.定时更新 # 对实时性要求不高
eg: 每隔12个小时,更新一下缓存(定时读取数据库,再写入缓存)
# 详见:
https://www.cnblogs.com/liuqingzheng/p/11080680.html
# 更新总结:
低一致性: 使用Redis自带的内存淘汰机制
高一致性: 主动更新, 并以超时剔除作为兜底方案
读操作:
缓存命中直接返回
缓存未命中则查询数据库, 并写入缓存, 设定超时时间
写操作:
先写数据库, 然后再删除缓存
要确保数据库与缓存操作的原子性
5.3 缓存粒度控制
# 缓存粒度:指的是缓存mysql表数据时,缓存全部字段属性还是部分字段属性
缓存全部属性
缓存部分重要属性
# 比较
1.通用性:全量属性更好
2.占用空间:部分属性更好
3.代码维护:表面上全量属性更好
# eg: 实际生产中 肯定是常用字段属性 不会全缓存
1.从mysql获取用户信息:select * from user where id=100
2.设置用户信息缓存:set user:100 select * from user where id=100
# 缓存粒度控制: "*" 还是 "某些字段"
8 redis实现布孔过滤器
9 python实现布隆过滤器
5.4 缓存穿透、缓存击穿、缓存雪崩
# 引言:
在生产环境中,会因为很多的原因造成 访问请求绕过了缓存,都需要访问数据库持久层
虽然对Redis缓存服务器不会造成影响,但是数据库的负载就会增大,使缓存的作用降低
# 实际生产:击穿和雪崩会有一丢丢可能遇见
# 缓存穿透(通常是恶意的)
# 描述:
缓存穿透是指缓存和数据库中都没有的数据
而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据
这时的用户很可能是攻击者,攻击会导致数据库压力过大
# 解决方案:
1.接口层校验
接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截 就不会访问到持久化层(mysql)
2.缓存空对象
从缓存和数据库中都取不到的数据,此时可以将key-value对写为key-null
同时设置缓存有效时间 设置短点,如30秒(设置太长会导致正常情况也没法使用)
这样可以防止攻击用户反复用同一个id暴力攻击
3.布隆过滤器拦截
在访问缓存层和存储层之前,将存在的所有key用布隆过滤器提前保存起来,做第一层拦截,
请求来了,先去布隆过滤器查,如果没有,表示非法,直接返回
# 缓存击穿
# 描述:
也叫作热点key问题, 就是一个被高并发访问, 并且缓存重建业务比较复杂的数据
缓存击穿是指缓存中没有 但数据库中有的热点数据 # 一般是缓存时间到期
这时由于并发用户特别多,同时读缓存没读到数据,
又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
# 解决方案:
1.设置热点数据永远不过期。
2.分布式互斥锁
只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可
# 缓存雪崩
# 描述:
指在同一时间段大量的缓存key同时失效或者Redis服务宕机
而查询数据量巨大,导致大量请求到达数据库, 引起数据库压力过大甚至down机。
和缓存击穿不同的是
缓存击穿指并发查同一条数据
缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
# 解决方案:
1.缓存数据的过期时间 设置随机,防止同一时间大量数据过期现象发生。
2.如果缓存数据库是分布式集群部署,将热点数据均匀分布在不同搞得缓存数据库中。
3.设置热点数据永远不过期。