卷友们好,我是今天的卷王阿秀。
既然如此,那就让我做今天最卷的崽儿吧。
其实几天前我在知乎回答完经典问题“你的编程能力从什么时候开始突飞猛进?时,底下就有很多人问过我是如何处理女朋友和学习之间的关系的。
当时我装逼口嗨了一下,说“女人,只会影响我写代码的速度”,其实我是开玩笑的。
相反,我很感谢我女朋友,感谢她的默默陪伴与理解,只是没写出来而已。
昨天又有人在底下问我这个问题,我也就正式回复了他一下。
其实我对象人很好,很理解我。有时候知道我晚上在看书或者写代码bug,也不吵着闹着让送她回去,节假日也不闹着我带她出去玩。
也正是因为她的知书达理才让我有时间和精力去看自己想看的书、刷自己想刷的视频、写自己想写的代码、打自己想打的力扣周赛等。
嘿嘿,不扯了,让我们开始卷吧。
本期是 Redis 第二期,至此 Redis 部分就全部更新完毕了,下一弹就是常见智力题与面试剖析了,估计还有两三期,整个逆袭进大厂系列就全部完结啦。
完结的时间,也会更新 PDF 的第四版,大家可以放心,第四版绝对值得期待!
第三版是 4.5 号推出的,这段时间很多小伙伴都专门跑来问我什么时候可以更新第四版。
别问,问就是快了。。。。
月底吧,我尽量在月底之前搞完,如果月底搞不完也正常,毕竟我经常鸽来着。
月底出不来六月初估计就能出来了,这个是最迟时间,绝对不鸽!要是鸽了,就剁了那啥啥...
话不多说,那我们就开始本期内容吧。
17、假如MySQL有1000万数据,采用Redis作为中间缓存,取其中的10万,如何保证Redis中的数据都是热点数据?
可以使用Redis的数据淘汰策略,Redis 内存数据集大小上升到一定大小的时候,就会施行这种策略。具体说来,主要有 6种内存淘汰策略:
- voltile-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-enviction(驱逐):禁止驱逐数据
18、Redis持久化机制可以说一说吗?
Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。
很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机 器、机器故障之后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
实现:单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。
以下有两种持久化机制
快照(snapshotting)持久化(RDB持久化)
Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行 备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性 能),还可以将快照留在原地以便重启服务器的时候使用。
快照持久化是Redis默认采用的持久化方式,在Redis.conf配置文件中默认有此下配置:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令 创建快照。 save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
AOF(append-only file)持久化
与快照持久化相比,AOF持久化的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启 AOF(append only file)方式的持久化,可以通过appendonly参数开启:appendonly yes
开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的 保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。
在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 appendfsync no #让操作系统决定何时进行同步
为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能 几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
Redis 4.0 对于持久化机制的优化
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。
如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
19、AOF重写了解吗?可以简单说说吗?
AOF重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态一样,但体积更小。
AOF重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有AOF文件进行任伺读 入、分析或者写入操作。
在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容 追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的 AOF文件,以此来完成AOF文件重写操作。
20、是否使用Redis集群,集群的原理是什么
Redis Sentinel(哨兵)着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入下线状态时,自动从服务器中选举出新的主服务器。
Redis Cluster(集群)着眼于扩展性,在单个Redis内存不足时,使用Cluster进行分片存储。
21、如何解决Redis的并发竞争Key问题
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!
推荐一种方案:分布式锁(zookeeper 和 Redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问 题,不要使用分布式锁,这样会影响性能)
基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的 与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。判断是否获取锁的方式很简单,只需要判断有 序节点中序号最小的一个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁 无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
在实践中,当然是从以可靠性为主。所以首推Zookeeper。
22、如何保证缓存与数据库双写时的数据一致性
首先说一句,你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如 何解决一致性问题?
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的 情况,最好不要做这个方案,最好将读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况。
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
最经典的缓存+数据库读写的模式,就是 预留缓存模式Cache Aside Pattern。
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
- 更新的时候,先删除缓存,然后再更新数据库,这样读的时候就会发现缓存中没有数据而直接去数据库中拿数据了。(因为要删除,狗日的编辑器可能会背着你做一些优化,要彻底将缓存中的数据进行删除才行)
互联网公司非常喜欢问这道面试题因为缓存在互联网公司使用非常频繁
在高并发的业务场景下,数据库的性能瓶颈往往都是用户并发访问过大。所以,一般都使用Redis做一个缓冲操作,让请求先访问到Redis,而不是直接去访问MySQL等数据库,从而减少网络请求的延迟响应。
23、数据为什么会出现不一致的情况?
这样的问题主要是在并发读写访问的时候,缓存和数据相互交叉执行。
一、单库情况下
同一时刻发生了并发读写请求,例如为A(写) B (读)2个请求
- A请求发送一个写操作到服务端,第一步会淘汰cache,然后因为各种原因卡主了,不在执行后面业务(例:大量的业务操作、调用其他服务处理消耗了1s)。
- B请求发送一个读操作,读cache,因为cache淘汰,所以为空
- B请求继续读DB,读出一个脏数据,并写入cache
- A请求终于执行完全,在写入数据到DB
总结:因最后才把写操作数据入DB,并没同步。cache里面一直保持脏数据
脏数据是指源系统中的数据不在给定的范围内或对于实际业务毫无意义,或是数据格式非法,以及在源系统中存在不规范的编码和含糊的业务逻辑。
二、主从同步,读写分离的情况下,读从库而产生脏数据
- A请求发送一个写操作到服务端,第一步会淘汰cache
- A请求写主数据库,写了最新的数据。
- B请求发送一个读操作,读cache,因为cache淘汰,所以为空
- B请求继续读DB,读的是从库,此时主从同步还没同步成功。读出脏数据,然后脏数据入cache
- 最后数据库主从同步完成
总结:这种情况下请求A和请求B操作时序没问题,是主从同步的时延问题(假设1s),导致读请求读取从库读到脏数据导致的数据不一致
根本原因:
单库下,逻辑处理中消耗1s,可能读到旧数据入缓存
主从+读写分离,在1s的主从同步时延中,到从库的旧数据入缓存
24、常见的数据优化方案你了解吗?
一、缓存双淘汰法
- 先淘汰缓存
- 再写数据库
- 往消息总线esb发送一个淘汰消息,发送立即返回。写请求的处理时间几乎没有增加,这个方法淘汰了缓存两次。因此被称为“缓存双淘汰法“,而在消息总线下游,有一个异步淘汰缓存的消费者,在拿到淘汰消息在1s后淘汰缓存,这样,即使在一秒内有脏数据入缓存,也能够被淘汰掉。
二、异步淘汰缓存
上述的步骤,都是在业务线里面执行,新增一个线下的读取binlog异步淘汰缓存模块,读取binlog总的数据,然后进行异步淘汰。
这里简单提供一个思路
1.思路:
MySQL binlog增量发布订阅消费+消息队列+增量数据更新到Redis
1)读请求走Redis:热数据基本都在Redis
2)写请求走MySQL: 增删改都操作MySQL
3)更新Redis数据:MySQ的数据操作binlog,来更新到Redis
2.Redis更新
1)数据操作主要分为两块:
- 一个是全量(将全部数据一次写入到Redis)
- 一个是增量(实时更新)
这里说的是增量,指的是 MySQL 的update、insert、delate变更数据。
这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新,就无需在从业务线去操作缓存内容。
结语
你学废了吗?祝你今天卷的愉快~