redis重点复习
1.AOF日志
5 04 | AOF日志:宕机了,Redis如何避免数据丢失?
Redis是先执行命令,把数据写入内存,然后才记录日志

AOF日志重写:当我们对一个列表先后做了6次修改操作后,列表的最后状态是[“D”, “C”, “N”],此时,只用LPUSH u:list “N”, “C”, "D"这一条命令就能实现该数据的恢复,这就节省了五条命令的空间。对于被修改过成百上千次的键值对来说,重写能节省的空间当然就更大了。
2.数据不一致

不一致原因:
-
查,命中从redis取出;没命中,查mysql,之后保存缓存
-
修改或保存,先删除缓存,再更新mysql
问题1:修改时,删除缓存,但是还没有更新mysql,此时查请求,发现没有命中缓存,查mysql之后保存缓存。(这样mysql就与redis数据不一致了)
-
查,命中从redis取出;没命中,查mysql,之后保存缓存
-
修改保存,先更新mysql,再删除缓存
问题2:查请求,没有命中缓存,查数据库,然后需要更新缓存;假如在查完之后更新缓存之前,有一个update请求,更新mysql,删除缓存,此时读请求做更新缓存。
https://blog.csdn.net/wd_boy/article/details/88289763
-
延时双删:1.del redis;2.update mysql;3.sleep;4.del redis
-
databus:insert、update、delete操作都是先更新mysql;根据binlog发送mq,更新redis
3.更新数据为什么不直接更新redis,而是删除呢?
没有并发:
1、先更新数据库,再更新缓存:如果更新数据库成功,但缓存更新失败,此时数据库中是最新值,但缓存中是旧值,后续的读请求会直接命中缓存,得到的是旧值。
2、先更新缓存,再更新数据库:如果更新缓存成功,但数据库更新失败,此时缓存中是最新值,数据库中是旧值,后续读请求会直接命中缓存,但得到的是最新值,短期对业务影响不大。但是,一旦缓存过期或者满容后被淘汰,读请求就会从数据库中重新加载旧值到缓存中,之后的读请求会从缓存中得到旧值,对业务产生影响。
如果有并发:
1、先更新数据库,再更新缓存,写+读并发:线程A先更新数据库,之后线程B读取数据,此时线程B会命中缓存,读取到旧值,之后线程A更新缓存成功,后续的读请求会命中缓存得到最新值。这种场景下,线程A未更新完缓存之前,在这期间的读请求会短暂读到旧值,对业务短暂影响。
2、先更新缓存,再更新数据库,写+读并发:线程A先更新缓存成功,之后线程B读取数据,此时线程B命中缓存,读取到最新值后返回,之后线程A更新数据库成功。这种场景下,虽然线程A还未更新完数据库,数据库会与缓存存在短暂不一致,但在这之前进来的读请求都能直接命中缓存,获取到最新值,所以对业务没影响。
3、先更新数据库,再更新缓存,写+写并发:线程A和线程B同时更新同一条数据,更新数据库的顺序是先A后B,但更新缓存时顺序是先B后A,这会导致数据库和缓存的不一致。
4、先更新缓存,再更新数据库,写+写并发:与场景3类似,线程A和线程B同时更新同一条数据,更新缓存的顺序是先A后B,但是更新数据库的顺序是先B后A,这也会导致数据库和缓存的不一致。
4.缓存穿透、雪崩、
缓存穿透
原因:查询不存在的key
解决办法:①.布隆过滤器;②.为不存在的值,添加默认值;③.以缓存为主,没有就是没有,有专门的异步线程去加载缓存(异步线程加载缓存,定期或者根据条件触发去加载)
缓存击穿
原因:某个key失效时,正好有大量请求
解决办法:①.更新缓存时,添加分布式锁,只能有一个线程去查mysql,然后更新缓存;②.以缓存为主,没有就是没有,有专门的异步线程去加载缓存(异步线程加载缓存,定期或者根据条件触发去加载)
缓存雪崩
原因1:大量缓存同一时间失效
解决办法:避免同一时间失效,设置过期时间时,加一个随机值,尽量避免同一时间大量过期;限流,服务降级,返回默认值
原因2:redis宕机了
解决办法:使用的热数据尽量分散在不同的机器;多个副本,实现高可用;限流,服务降级,返回默认值
5.缓存污染
在一些场景下,有些数据被访问的次数非常少,甚至只会被访问一次。当这些数据服务完访问请求后,如果还继续留存在缓存中的话,就只会白白占用缓存空间。这种情况,就是缓存污染。
如何解决:
回忆一下缓存淘汰策略:
volatile-lru :从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中随机挑选数据淘汰。
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰。
allkeys-random:从数据集(server.db[i].dict)中随机挑选数据淘汰。
no-envivtion(驱逐):禁止驱逐数据。
Redis 从 4.0 版本开始增加了 LFU 淘汰策略:
LFU 缓存策略是在 LRU 策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。
6.RDB日志
6 05 | 内存快照:宕机后,Redis如何实现快速恢复?
Redis提供了两个命令来生成RDB文件,分别是save和bgsave。
-
save:在主线程中执行,会导致阻塞;
-
bgsave:创建一个子进程,专门用于写入RDB文件,避免了主线程的阻塞,这也是Redis RDB文件生成的默认配置。
7.生成RDB快照文件时,redis可以有写操作么?
Redis就会借助操作系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。如果主线程要修改一块数据(例如图中的键值对C),那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave子进程会把这个副本数据写入RDB文件,而在这个过程中,主线程仍然可以直接修改原来的数据。
8.Redis崩溃恢复
Redis 4.0中提出了一个混合使用AOF日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用AOF日志记录这期间的所有命令操作。
9.主从同步
主从从模式
-
从库发送同步命令
-
主库生成RDB快照文件,同步到从库
-
同步这段时间的写操作,主库先放到缓存,然后从库加载完所有数据后,再把这部分数据写到从库
10.主从断网
-
2.8之前,再全量同步一次
-
2.8之后,增量同步。具体实现,主库会把写操作写到环形缓冲区,主库记录自己写的位置,从库记录自己读到的位置。(所以当这个缓冲区比较小时,如果主从同步超过了这个缓冲区,就会造成主从数据不一致问题)
11.主从数据不一致解决
-
业务可以接受,忽略
-
强制走主库
-
12.哨兵机制
监控、选主、通知
判断主库是否下线:哨兵集群,少数服从多数
选主:筛选、打分最后定主库(网络好坏,主从同步快慢)
13.事务
当命令不正确时,事务所有语句都会回滚
当数据结构不正确时,事务前后都会被执行,不会回滚
14.Redis Cluster
一个切片集群共有16384个哈希槽,哈希槽类似于分区,每个键值对都会根据它的key,被映射到一个哈希槽中。
Redis实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。
客户端和集群建立连接后,实例就会把哈希槽的分配信息发给客户端。客户端收到哈希槽信息后,会把哈希槽信息缓存在本地
Redis Cluster重定向:客户端给一个实例发送数据读写操作时,这个实例上并没有相应的数据,客户端要再给一个新实例发送操作命令。
1.当客户端把一个键值对的操作请求发给一个实例时,如果这个实例上并没有这个键值对映射的哈希槽,那么,这个实例就会给客户端返回下面的MOVED命令响应结果,这个结果中就包含了新实例的访问地址。
2.通过返回的MOVED命令,就相当于把哈希槽所在的新实例的信息告诉给客户端了。这样一来,客户端就可以直接和172.16.19.5连接,并发送操作请求了。
Ask
1.客户端向实例2发送请求,但此时,Slot 2中的数据只有一部分迁移到了实例3,还有部分数据没有迁移。
2.客户端就会收到一条ASK报错信息GET hello:key (error) ASK 13320 172.16.19.5:6379
这个结果中的ASK命令就表示,客户端请求的键值对所在的哈希槽13320,在172.16.19.5这个实例上,但是这个哈希槽正在迁移。
3.客户端需要先给172.16.19.5这个实例发送一个ASKING命令。这个命令的意思是,让这个实例允许执行客户端接下来发送的命令。
4.客户端再向这个实例发送GET命令,以读取数据。
ASK命令表示两层含义:第一,表明Slot数据还在迁移中;第二,ASK命令把客户端所请求数据的最新实例地址返回给客户端,此时,客户端需要给实例3发送ASKING命令,然后再发送操作命令。
和MOVED命令不同,ASK命令并不会更新客户端缓存的哈希槽分配信息。
15.Redis数据淘汰机制
https://www.cnblogs.com/tqlin/p/11864644.html
Redis在每个服务客户端执行一个命令的时候,都会先检测使用的内存是否超额。
在Redis中,我们可以设置Redis的最大使用内存大小(server.maxmemory)。当Redis内存数据集大小上升到一定程度的时候,就会施行数据淘汰机制。Redis提供了一下6种数据淘汰机制:
volatile-lru :从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中随机挑选数据淘汰。
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰。
allkeys-random:从数据集(server.db[i].dict)中随机挑选数据淘汰。
no-envivtion(驱逐):禁止驱逐数据。
LRU机制: Redis保存了lru计数器server.lrulock,会定时的去更新(redis定时程序severCorn()),每个Redis对象都会设置相应的lru值,每次访问对象的时候,Redis都会更新redisObject.lru。
LRU淘汰机制: 在数据集中随机挑选几个键值对,取出其中lru最大的键值对淘汰。所以,Redis并不能保证淘汰的数据都是最近最少使用的,而是随机挑选的键值对中的。
TTL机制: Redis数据集结构中保存了键值对过期时间表,即 redisDb.expires。
TTL淘汰机制: 在数据集中随机挑选几个键值对,取出其中最接近过期时间的键值对淘汰。所以,Redis并不能保证淘汰的数据都是最接近过期时间的,而是随机挑选的键值对中的。
自己实现一个LRU算法:
-
一个队列用于存放所有数据
-
当用户访问某个数据时,把队列里的数据取出放在表头
-
当数据满时,把表尾的数据清除
-
16.秒杀系统设计
1.秒杀开始前:静态页面
2.秒杀开始:判断库存是否足够
- 库存放到redis缓存中
- redis判断库存是否充足
- 如果库存足够,扣减库存。注意,第二步和第三步需要原子操作,所以使用Lua脚本实现
3.秒杀下单:(因为获取到购买资格的用户比较少,所以mysql可以支撑)
- 用户获得商品购买资格
- 下单、支付使用mysql,需要使用事务保证数据一致性,错误之后进行回滚
浙公网安备 33010602011771号