安装
拉取redis镜像
docker pull redis:latest创建文件夹
mkdir /usr/local/docker/redis并进入到该文件夹
cd /usr/local/docker/redis将redis.conf文件上传到该文件夹下,并修改以下内容
69 #bind 127.0.0.1 #注释掉这行,让其它网络设备可以访问到redis,不然只有本机能连接上redis 88 protected-mode no #关闭redis自我保护模式 257 logfile "/data/redis.log" #指定日志文件 786 requirepass 123456 #设置密码启动Redis
docker run -itd --name redis -p 6379:6379 --restart=always -v /usr/local//docker/redis/redis.conf:/etc/redis/redis.conf redis redis-server /etc/redis/redis.conf-p 6379:6379:把容器内的6379端口映射到宿主机6379端口
–restart=always:启动docker时启动该容器
-v /usr/local/redis/redis.conf:/etc/redis/redis.conf:把宿主机配置好的redis.conf放到容器内的这个位置中
redis-server /etc/redis/redis.conf:按照我们自己的这个redis.conf的配置启动Redis
开放端口
firewall-cmd --zone=public --add-port=6379/tcp --permanent systemctl restart firewalld.service在Windows中利用Redis Windows的redis-cli.exe连接docker中的redis
redis-cli.exe -h 192.168.174.130 -p 6379输入密码进行认证
进行redis操作
Redis持久化之RDB
Redis持久化
Redis是一种高级key-value数据库。它跟memcached类似,不过数据可以持久化,而且支持的数据类型很丰富。有字符串,链表,集 合和有序集合。支持在服务器端计算集合的并,交和补集(difference)等,还支持多种排序功能。所以Redis也可以被看成是一个数据结构服务器。
Redis的所有数据都是保存在内存中,然后不定期的通过异步方式保存到磁盘上(这称为“半持久化模式”);也可以把每一次数据变化都写入到一个append only file(aof)里面(这称为“全持久化模式”)。
由于Redis的数据都存放在内存中,如果没有配置持久化,redis重启后数据就全丢失了,于是需要开启redis的持久化功能,将数据保存到磁 盘上,当redis重启后,可以从磁盘中恢复数据。redis提供两种方式进行持久化,一种是RDB持久化(原理是将Reids在内存中的数据库记录定时 dump到磁盘上的RDB持久化),另外一种是AOF(append only file)持久化(原理是将Reids的操作日志以追加的方式写入文件)。
RDB:在指定的时间间隔能对你的数据进行快照存储。(默认开启)
AOF:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。(默认关闭)
RDB(快照)
持久化配置
打开redis.conf配置文件
RDB的持久化配置
304、305、306、336、339、362行
# 时间策略 save 900 1 save 300 10 save 60 10000 # 导入时是否检查 rdbchecksum yes # 文件名称 dbfilename dump.rdb # 文件保存路径 挡在容器data目录下 dir /data配置解释
save 900 1 表示900s内如果有1条是写入命令,就触发产生一次快照,可以理解为就进行一次备份
save 300 10 表示300s内有10条写入,就产生快照
下面的类似,那么为什么需要配置这么多条规则呢?因为Redis每个时段的读写请求肯定不是均衡的,为了平衡性能与数据安全,我们可以自由定制什么情况下触发备份。所以这里就是根据自身Redis写入情况来进行合理配置。
当然如果想要禁用RDB配置,也是非常容易的,只需将save 900 1等注释起来,然后在save的最后一行写上:save “”
RDB的原理
在Redis中RDB持久化的触发分为两种:自己手动触发与Redis定时触发。
针对RDB方式的持久化,手动输入以下命令完成手动持久化:
save:会阻塞当前Redis服务器,直到持久化完成,线上应该禁止使用。
bgsave:该触发方式会fork一个子线程,由子进程负责持久化过程,因此阻塞只会发生在fork子进程的时候。
而自动触发的场景主要是有以下几点:
根据我们的 save m n 配置规则自动触发;
从节点全量复制时,主节点发送rdb文件给从节点完成复制操作,主节点会触发 bgsave;
执行 debug reload 时;
执行 shutdown时,如果没有开启aof,也会触发。
由于 save 基本不会被使用到,我们重点看看 bgsave 这个命令是如何完成RDB的持久化的。
这里注意的是 fork 操作会阻塞,导致Redis读写性能下降。我们可以控制单个Redis实例的最大内存,来尽可能降低Redis在fork时的事件消耗。
Redis持久化之AOF
AOF的配置
AOF是redis的一种持久化方式,用来记录所有的写操作,但是随着时间增加,aof文件会越来越大,所以需要进行重写,将内存中的数据重新以命令的方式写入aof文件。
在重写的过程中,由于redis还会有新的写入,为了避免数据丢失,会开辟一块内存用于存放重写期间产生的写入操作,等到重写完毕后会将这块内存中的操作再追加到aof文件中。
修改1059行将no改为yes开启aof持久化方式
1059、1063、1089、1130、1131
# 是否开启aof appendonly yes # 文件名称 appendfilename "appendonly.aof" # 同步方式 appendfsync everysec # 重写触发配置 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb配置解释
appendfsync everysec 它其实有三种模式:
always 把每个写命令都立即同步到aof,很慢,但是很安全
everysec 每秒同步一次,是折中方案
no redis不处理交给OS来处理,非常快,但是也最不安全
一般情况下都采用 everysec 配置,这样可以兼顾速度与安全,最多损失1s的数据。
auto-aof-rewrite-percentage 是设置aof rewrite触发时机的一个参数,当当前的aof文件大小超过上一次rewrite后aof文件的百分比后触发rewrite
例如将100改为800 ,即当前的aof文件超过上一次重写后aof文件的8倍时才会再次rewrite,这样可以保证短期内不会再次进行重写操作。
auto-aof-rewrite-min-size
aof文件重写最小的文件大小,即最开始aof文件必须要达到这个文件时才触发,后面的每次重写就不会根据这个变量了(根据上一次重写完成之后的大小).此变量仅初始化启动redis有效.如果是redis恢复时,则lastSize等于初始aof文件大小.
修改之后要重启redis
向redis中添加一些key-value
然后在redis文件夹下查看是否有appendonly.aof文件
利用appendonly.aof文件恢复数据
关闭redis
重启redis
连接redis,输入keys *查看数据
可以看到数据只有appendonly.aof中保存的数据,redis启动时会先检查AOF文件是否存在,如果不存在就尝试加载RDB。那么为什么会优先加载AOF呢?因为AOF保存的数据更完整,通过上面的分析我们知道AOF基本上最多损失1s的数据。
可以删除appendonly.aof文件,然后在重启redis看看数据有哪些
AOF的原理
由于AOF 文件都是追加的,随着服务器的运行 AOF 文件会越来越大,体积过大的 AOF 文件对 redis 服务器甚至是主机都会有影响,而且在 Redis 重启时加载过大的 AOF 文件需要过多的时间,这些都是不友好的,那 Redis 是如何解决这个问题的呢?Redis 引入了重写机制来解决 AOF 文件过大的问题。
AOF的整个流程大体来看可以分为两步,一步是命令的实时写入(如果是 appendfsync everysec 配置,会有1s损耗),第二步是对aof文件的重写。
对于增量追加到文件这一步主要的流程是:命令写入→追加到aof_buf→同步到aof磁盘。那么这里为什么要先写入buf在同步到磁盘呢?如果实时写入磁盘会带来非常高的磁盘IO,影响整体性能。
Redis AOF 文件重写是把 Redis 进程内的数据转化为写命令同步到新 AOF 文件的过程,重写之后的 AOF 文件会比旧的 AOF 文件占更小的体积,这是由以下几个原因导致的:
进程内已经超时的数据不再写入文件
旧的 AOF 文件含有无效命令,如 del key1、hdel key2、srem keys、set a111、set a222等。重写使用进程内数据直接生成,这样新的AOF文件只保 留最终数据的写入命令
多条写命令可以合并为一个,如:lpush list a、lpush list b、lpush list c可以转化为:lpush list a b c。为了防止单条命令过大造成客户端缓冲区溢 出,对于 list、set、hash、zset 等类型操作,以64个元素为界拆分为多条。
重写之后的 AOF 文件体积更小了,不但能够节约磁盘空间,更重要的是在 Redis 数据恢复时,更小体积的 AOF 文件加载时间更短。AOF 文件重写跟 RDB 持久化一样分为手动触发和自动触发,手动触发直接调用 bgrewriteaof 命令就好了,我们后面会详细聊一聊这个命令,自动触发就需要我们在 redis.conf 中修改以下几个配置
auto-aof-rewrite-percentage 800
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percentage:代表当前 AOF文件空间 (aof_current_size)和上一次重写后 AOF 文件空间(aof_base_size)的比值,默认是 100%,也就是一样大的时候
auto-aof-rewrite-min-size:表示运行 AOF 重写时 AOF 文件最小体积,默认为 64MB,也就是说 AOF 文件最小为 64MB 才有可能触发重写
满足了这两个条件,Redis 就会自动触发 AOF 文件重写,AOF 文件重写的细节跟 RDB 持久化生成快照有点类似,下面是 AOF 文件重写流程图:
在重写期间,由于主进程依然在响应命令,为了保证最终备份的完整性;因此它依然会写入旧的AOF file中,如果重写失败,能够保证数据不丢失。
为了把重写期间响应的写入信息也写入到新的文件中,因此也会为子进程保留一个buf,防止新写的file丢失数据。
重写是直接把当前内存的数据生成对应命令,并不需要读取老的AOF文件进行分析、命令合并。
AOF文件直接采用的文本协议,主要是兼容性好、追加方便、可读性高可认为修改修复。
不论是RDB还是AOF都是先写入一个临时文件,然后通过 rename 完成文件的替换工作。
RDB与AOF的区别
RDB存在哪些优势
一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
相比于AOF机制,如果数据集很大,RDB的启动效率会更高。
RDB又存在哪些劣势
如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
AOF的优势
该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3种同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变 化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。
由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof(./redis-check-aof --fix appendonly.aof)工具来帮助我们解决数据一致性的问题。
如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。
AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。
AOF的劣势
对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。
二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。
最后
我们的Redis必须使用数据持久化吗?如果我们的Redis服务器只作为缓存使用,Redis中存储的所有数据都是从其他地方同步过来的备份,那么就没必要开启数据持久化的选项。
不过大多数应用场景下,建议至少开启RDB方式的数据持久化。Redis 对于数据备份是非常友好的, 因为我们可以在服务器运行的时候对 RDB 文件进行复制: RDB 文件一旦被创建, 就不会进行任何修改。
当服务器要创建一个新的 RDB 文件时, 它先将文件的内容保存在一个临时文件里面, 当临时文件写入完毕时,程序才使用 rename 原子地用临时文件替换原来的 RDB 文件。
将docker中持久化文件放到宿主机本地
因为容器实例的运行是有生命周期的,所以一些redis的备份、日志和配置文件什么的最好还是放在服务器本地。这样当容器删除时,我们也可以保留备份和日志文件。
停止删除Redis容器
docker stop redis docker rm redis在/usr/local/redis下创建data文件夹,用来保存持久化的数据
mkdir /usr/local/redis/data重新启动一个Redis容器并指定持久化文件保存的位置
docker run -itd --name redis -p 6379:6379 --restart=always -v /usr/local/redis/redis.conf:/etc/redis/redis.conf -v /usr/local/redis/data:/data redis redis-server /etc/redis/redis.conf-v /usr/local/redis/data:/data:将redis中/data文件夹下的文件保存到/usr/local/redis/data中
通过Windows重新连接Redis并进行相关操作(bgsave),然后查看/usr/local/redis/data文件夹中是否有持久化文件
如果有以下内容表示成功
主从复制
什么是主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的作用
数据冗余 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
故障恢复 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
负载均衡 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
读写分离 可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量;
高可用基石 除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
常见主从结构
- 一主一从:用于主节点故障转移从节点,当主节点的“写”命令并发高且需要持久化,可以只在从节点开启AOF(主节点不需要),这样即保证了数据的安全性,也避免持久化对主节点的影响
- 一主多从:针对“读”较多的场景,“读”由多个从节点来分担,但节点越多,主节点同步到多节点的次数也越多,影响带宽,也加重主节点的稳定
- 树状主从:一主多从的缺点(主节点推送次数多压力大)可用些方案解决,主节点只推送一次数据到从节点B,再由从节点B推送到C,减轻主节点推送的压力。
实例演示
在一台电脑上模拟多个redis服务器。一般至少需要一主两从
将redis.conf拷贝一份并命名为redis6379.conf
修改以下内容
92 prot 6379 244 pidfile /var/run/redis_6379.pid 257 logfile "redis_6379.log" 339 dbfilename dump_6379.rdb 1063 appendfilename "appendonly_6379.aof将redis6379.conf拷贝两份,并分别命名为redis6380.conf、redis6381.conf
将redis6380.conf、redis6381.conf两个文件中的6379分别替换为6380和6381
替换指令
:%s/6379/6380/g修改6379、6380、6381的主机密码
392 masterauth 123456先删除原来的redis容器
docker stop redis docker rm redis分别启动三台redis
docker run -itd --net=host --name redis-6379 -p 6379:6379 --restart=always -v /usr/local/redis/redis6379.conf:/etc/redis/redis.conf -v /usr/local/redis/data:/data redis redis-server /etc/redis/redis.conf docker run -itd --net=host --name redis-6380 -p 6380:6380 --restart=always -v /usr/local/redis/redis6380.conf:/etc/redis/redis.conf -v /usr/local/redis/data:/data redis redis-server /etc/redis/redis.conf docker run -itd --net=host --name redis-6381 -p 6381:6381 --restart=always -v /usr/local/redis/redis6381.conf:/etc/redis/redis.conf -v /usr/local/redis/data:/data redis redis-server /etc/redis/redis.conf开放6380和6381端口
firewall-cmd --zone=public --add-port=6380/tcp --permanent firewall-cmd --zone=public --add-port=6381/tcp --permanent systemctl restart firewalld.service通过Windows连接到三个Redis,并通过info replication指令查看每个redis的角色信息
6379
6380
6381
可以看到此时所有redis都是主机,它们之间没有任何关系
在6380和6381上通过slaveof 192.168.174.130 6379将自己设置为6379的从机
成功之后分别在6379、6380、6381上通过info replication查看当前redis角色
在6379上可以看到它下面有两个从机6380、6381
在6379上设置key,然后在6380、6381上查看是否有相同的key,如果有表明成功
哨兵模式
哨兵模式
当主机在停机期间怎么实现功能能正常使用呢?使用哨兵机制(哨兵模式)
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
哨兵模式概述
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里的哨兵有两个作用
通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
在/usr/local/redis目录下创建sentinel.conf,并添加以下内容
bind 0.0.0.0 protected-mode no # 关闭保护模式,方便测试 port 16379 # 哨兵的端口 dir "/var/log" logfile "/var/log/16379.log" sentinel monitor mymaster 192.168.174.130 6379 1 #1:至少几个哨兵认为主机下线时进行故障切换 sentinel auth-pass mymaster 123456创建哨兵容器
docker run -d --net=host --name redis-sentinel -v /usr/local/redis/sentinel.conf:/conf/sentinel.conf -v /usr/local/redis/sentinel/log:/var/log --network host redis redis-sentinel /conf/sentinel.conf开启端口
firewall-cmd --zone=public --add-port=16379/tcp --permanent systemctl restart firewalld.service查看sentinel日志信息,日志文件在/usr/local/redis/sentinel/log中
可以看到sentinel已经监控上了所有redis
关闭主机redis-6379
docker stop redis-6379观察sentinel日志信息,出现以下信息表明sentinel正在进行故障切换,并且切换成功
为了方便关闭删除相关容器,可以通过shell脚本简化操作
在/usr/local/redis下创建stop.sh文件
#!/bin/bashdockerstop redis-sentineldockerrmredis-sentinelPORT=6379ENDPORT=6381while[$((PORT<=ENDPORT))!="0"];doecho"Stop and RM Redis$PORT"dockerstop redis-$PORTdockerrmredis-$PORTPORT=$((PORT+1))doneecho"done"exit0给该文件添加执行权限
chmod +x stop.sh执行
./stop.shSpringBoot整合Sentinel
注意:
主从复制时slaveof指令后的ip应该为网络ip(虚拟机IP),不要写成127
哨兵配置文件中主机的IP也应该是网络ip(虚拟机IP),不要写127
配置哨兵
需要注意的是:所有哨兵的配置文件中一定要关闭保护模式
protected-mode no #关闭保护模式,保护redis服务器
在项目主配置文件中配置哨兵
mybatis:config-location:classpath:mybatis/mybatis-config.xmltype-aliases-package:com.woniuxy.entityspring:datasource:driver-class-name:com.mysql.cj.jdbc.Driverurl:jdbc:mysql://localhost:3306/redis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=trueusername:rootpassword:rootredis:# port: 6379# host: 192.168.174.131 # 虚拟机ipjedis:pool:max-idle:8# 最大发呆(空闲)的连接数max-wait:-1ms# 最大等待时间 -1代表一直登陆max-active:8# 最大活跃的连接数 最大连接数min-idle:2# 最小发呆的连接数sentinel:master:mymasternodes:192.168.174.130:16379server:port:8081课外补充
Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。使用前面介绍的Dockerfile我们很容易定义一个单独的应用容器。然而在日常开发工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器;再比如在分布式应用一般包含若干个服务,每个服务一般都会部署多个实例。如果每个服务都要手动启停,那么效率之低、维护量之大可想而知。这时候就需要一个工具能够管理一组相关联的的应用容器,这就是Docker Compose。
- 在
/usr/local/redis目录下创建docker-compose.yml文件并添加以下内容
version:'2'services:master:image:rediscontainer_name:redis-6379command:redis-server /etc/redis/redis.confvolumes:-/usr/local/redis/data/redis-6379:/data-/usr/local/redis/redis6379.conf:/etc/redis/redis.confnetwork_mode:"host"slave1:image:rediscontainer_name:redis-6380command:redis-server /etc/redis/redis.conf--slaveof 192.168.174.130 6379volumes:-/usr/local/redis/data/redis-6380:/data-/usr/local/redis/redis6380.conf:/etc/redis/redis.confdepends_on:-masternetwork_mode:"host"slave2:image:rediscontainer_name:redis-6381command:redis-server /etc/redis/redis.conf--slaveof 192.168.174.130 6379volumes:-/usr/local/redis/data/redis-6381:/data-/usr/local/redis/redis6381.conf:/etc/redis/redis.confdepends_on:-masternetwork_mode:"host"sentinel1:image:rediscontainer_name:redis-sentinelcommand:redis-sentinel /usr/local/redis/sentinel.confvolumes:-/usr/local/redis/sentinel/log:/var/log-/usr/local/redis/sentinel.conf:/usr/local/redis/sentinel.confnetwork_mode:"host"depends_on:-master-slave1-slave2将docker-compose文件拷贝到/usr/local/bin目录下
给docker-compose文件添加执行权限
sudo chmod +x /usr/local/bin/docker-compose- 创建软连接
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose- 查看docker-compose版本
docker-compose --version- 后台运行docker-compose
docker-compose up -d三大问题
缓存穿透
产生原因
程序在处理缓存时,一般是先从缓存查询,如果缓存没有这个key获取为null,则会从DB中查询,并设置到缓存中去。
按这种做法,那查询一个一定不存在的数据值,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决办法
最好对于每一个缓存key都有一定的规范约束,这样在程序中对不符合parttern的key 的请求可以拒绝。(但一般key都是通过程序自动生成的)
将可能出现的缓存key的组合方式的所有数值以hash形式存储在一个很大的bitmap中<布隆过滤器>(需要考虑如何将这个可能出现的数据的hash值之后同步到bitmap中,后端每次新增一个可能的组合就同步一次,或者穷举),一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
常用:如果对应在数据库中的数据都不存在,我们将此key对应的value设置为一个默认的值,比如“NULL”,并设置一个缓存的失效时间。当然这个key的时效比正常的时效要小的多
缓存雪崩
产生原因
指的是大量缓存集中在一段时间内失效,发生大量的缓存击穿,所有的查询都落在数据库上,造成了缓存雪崩。
正常情况
当大量数据失效或者redis宕机
解决办法
这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布,设置不同的过期时间。缓存的过期时间用随机值,尽量让不同的key的过期时间不同(例如:定时任务新建大批量key,设置的过期时间相同)
可以把缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinel或cluster实现。
采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底
缓存击穿
产生原因
缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,瞬间对数据库的访问压力增大。
缓存击穿这里强调的是并发,造成缓存击穿的原因有以下两个:
该数据没有人查询过 ,第一次就大并发的访问。(冷门数据)
添加到了缓存,reids有设置数据失效的时间 ,这条数据刚好失效,大并发访问(热点数据)
对于缓存击穿的解决方案就是加锁,具体实现的原理图如下:
当用户出现大并发访问的时候,在查询缓存的时候和查询数据库的过程加锁,只能第一个进来的请求进行执行,当第一个请求把该数据放进缓存中,接下来的访问就会直接集中缓存,防止了缓存击穿。
业界比价普遍的一种做法,即根据key获取value值为空时,锁上,从数据库中load数据后再释放锁。若其它线程获取锁失败,则等待一段时间后重试。
解决办法
与缓存雪崩的解决方法类似: 用加锁或者队列的方式保证缓存的单线程(进程)写,在加锁方法内先从缓存中再获取一次,没有再查DB写入缓存。
还有一种比较好用的(针对缓存雪崩与缓存击穿):
物理上的缓存是不设置超时时间的(或者超时时间比较长),但是在缓存的对象上增加一个属性来标识超时时间(此时间相对小)。当获取到数据后,校验数据内部的标记时间,判定是否快超时了,如果是,异步发起一个线程(控制好并发)去主动更新该缓存。
这种方式会导致一定时间内,有些请求获取缓存会拿到过期的值,看业务是否能接受而定。
数据淘汰
淘汰策略
热点数据
在现今的电商平台中都有会有些大卖的商品,我们称之为简爆品。这些商品会有个特点,就是访问量(查询)特别大。我们专业上面可以称之为热点数据,在处理这些热点商品时,系统需要做一些特殊的处理。我们常见的处理方式就是将这些数据放到redis缓存中去,但是redis是一种内存数据库,而内存的容量又是有限的。随着业务量的增长,我们放在Redis里面的数据越来越多了,导致内存严重不足,这个时候就需要我们将Redis缓存中一些不是那么常用的数据移除掉,为了更好的利用内存,使Redis存储的都是缓存的热点数据,Redis设计了相应的内存淘汰机制(也可以叫做缓存淘汰机制)。
开启淘汰机制
修改redis缓存大小,单位是字节
内存淘汰的过程
首先,客户端发起了需要申请更多内存的命令(如set)。
然后,Redis检查内存使用情况,如果已使用的内存大于maxmemory则开始根据用户配置的不同淘汰策略来淘汰内存(key),从而换取一定的内存。
最后,如果上面都没问题,则这个命令执行成功。
提示:maxmemory为0的时候表示我们对Redis的内存使用没有限制。
淘汰策略
LRU(Least Recently Used)
| 策略 | 解释 |
|---|---|
| volatile-lru | 从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。注意:redis并不是保证取得所有数据集中最近最少使用的键值对,而只是随机挑选的几个键值对中的, 当内存达到限制的时候无法写入非过期时间的数据集。 |
| volatile-ttl | 从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。注意:redis 并不是保证取得所有数据集中最近将要过期的键值对,而只是随机挑选的几个键值对中的, 当内存达到限制的时候无法写入非过期时间的数据集。 |
| volatile-random | 从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制的时候无法写入非过期时间的数据集。 |
| allkeys-lru | 从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰。当内存达到限制的时候,对所有数据集挑选最近最少使用的数据淘汰,可写入新的数据集。 |
| allkeys-random | 从数据集(server.db[i].dict)中任意选择数据淘汰,当内存达到限制的时候,对所有数据集挑选随机淘汰,可写入新的数据集。 |
| no-enviction | 当内存达到限制的时候,不淘汰任何数据,不可写入任何数据集,所有引起申请内存的命令会报错。 |
淘汰策略选择
根据使用经验, 一般来说回收策略可以这样来配置:
allkeys-lru:如果期望用户请求呈现幂律分布(power-law distribution),也就是,期望一部分子集元素被访问得远比其他元素多时,可以使用allkeys-lru策略。在你不确定时这是一个好的选择。
allkeys-random:如果期望是循环周期的访问,所有的键被连续扫描,或者期望请求符合平均分布(每个元素以相同的概率被访问),可以使用allkeys-random策略。
volatile-ttl:如果你期望能让 Redis 通过使用你创建缓存对象的时候设置的TTL值,确定哪些对象应该是较好的清除候选项,可以使用volatile-ttl策略。
另外值得注意的是,为键设置过期时间需要消耗内存,所以使用像allkeys-lru这样的策略会更高效,因为在内存压力下没有必要为键的回收设置过期时间。
淘汰个数
删除策略
数据删除策略有三种
被动删除:只有key被操作时(如GET),Redis才会被动检查该key是否过期,如果过期则删除之并且返回NIL。
主动删除:定期删除过期的数据
默认是10,表示serverCron每秒执行10次
注意:hz调大将会提高Redis主动淘汰的频率,如果Redis存储中包含很多冷数据占用内存过大的话,可以考虑将这个值调大,但Redis作者建议这个值不要超过100。
hz用于计算LRU信息并清除过期key,关闭超时的客户端连接,执行RDB或AOF持久化相关操作,更新统计信息等- 当前已用内存超过maxmemory限定时,触发数据淘汰策略
被动删除特点
这种删除策略对CPU是友好的,删除操作只有在不得不的情况下才会进行,不会其他的expire key上浪费无谓的CPU时间。
但是这种策略对内存不友好,一个key已经过期,但是在它被操作之前不会被删除,仍然占据内存空间。如果有大量的过期键存在但是又很少被访问到,那会造成大量的内存空间浪费。