Java八股
Redis篇
如何解决缓存穿透,击穿,雪崩问题?
缓存穿透:查询一个不存在的数据,mysql查询不到数据,就会导致每次请求都查询数据库,给数据库带来很大的压力
解决方案:1.缓存空数据,占用内存,并且导致数据不一致。
2.布隆过滤器,主要是用于检索一个元素是否在一个集合中,使用的是redisson实现的布隆过滤器。底层是先去初始化一个比较大数组,里面存放二进制0或1,在一开始都是0,当一个key来了之后经过3次hash计算,根据数据长度取模找到数据的下标,将数组中原来的0改成1,这样的话,三个数组的位置就能标明一个key的存在。布隆过滤器有可能会产生一定的误判,可以设置这个误判率,大概率不会超过5%。
缓存击穿:给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发请求可能会瞬间把数据库压垮。
解决方案:1.互斥锁(强一致,性能差),当缓存失效时,先使用Redis的setnx去设置一个互斥锁,再去查找数据库并且重建缓存,释放锁。有可能产生死锁的问题
2.逻辑过期(高可用,性能优),在设置key的时候,设置一个过期时间字段一起存入缓存,不给当前key设置过期时间。
缓存雪崩:在同一时间段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:1.给不同的key的TTL添加随机值。2.利用Redis集群提高服务的可用性。3.给缓存业务添加降级限流策略。4.给业务添加多级缓存
mysql的数据如何与redis进行同步呢?(强一致性)
使用的是Redisson实现的读写锁。在读的时候添加共享锁,其他线程可以共享读操作。当我们更新数据的时候,添加排他锁,阻塞其他线程读写操作,这样就能保证在写数据的同时,是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是,读方法和写方法上需要使用同一把锁才行。
其实排他锁底层使用的也是SETNX
,它保证了同时只能有一个线程操作锁住的方法。
延迟双删,如果是写操作,我们先把缓存中的数据删除,然后更新数据库,最后再延时删除缓存中的数据。其中,这个延时多久不太好确定。在延时的过程中,可能会出现脏数据,并不能保证强一致性,所以没有采用它。
mysql的数据如何与redis进行同步呢?(延迟一致性)
使用MQ中间件,更新数据之后,通知缓存删除
redis的数据持久化
1.RDB:
例子: 想象一下相机的快门点击。每当你点击快门,你都会捕捉到那个特定时刻的场景。RDB的工作方式很相似,只不过它捕捉的是数据的状态。
Redis 主进程(父进程)会执行 fork 操作来创建一个子进程,子进程会获得父进程内存中的数据副本,父进程可以立即返回处理其他客户端请求,而不需要等待 RDB 的生成过程,操作系统使用写时复制(Copy-On-Write, COW)技术,任何在父进程(Redis主进程)上发生的写操作不会影响子进程中的数据。这确保了子进程中的数据是隔离的,不受父进程中数据更改的影响。当 Redis 重新启动时,如果配置为使用 RDB 持久化,它会查找 RDB 文件,并加载它。由于 RDB 文件是一个紧凑的二进制表示形式,数据加载非常快
2.AOF:
例子:想象你正在写日记,每次有新的事件,你就写下来。AOF 就像是 Redis 的日记,记录了所有的写操作命令。
AOF 持久化的实现主要是以上三步:命令追加、文件写入、文件同步
- 命令追加: 将 redis 写操作命令追加到 aof_buf 缓冲区
- 文件写入: 周期性地将 aof_buf 缓冲区的命令写入 AOF 文件的内核缓冲区。
- 文件同步:根据配置同步策略,将 AOF 文件缓冲区的内容同步到磁盘。
其中文件同步策略 redis 提供了三种,分别是以下三种:
always:每次有命令写入时都立即同步。这提供了最高的数据安全性,但效率最低。
everysec:每秒同步一次。这是一个权衡安全性和效率的策略。最多只丢失 1 秒 的数据
no:让操作系统决定最佳的同步时间。这可能导致数据丢失,但提供了最高的效率。
这里顺带提个问题:假如 Redis 的 RDB 和 AOF 持久化都启用,redis 在载入数据的时候,是载入 AOF 文件?还是 RDB 文件?
我直接说答案:Redis 会优先载入 AOF 文件来恢复数据,而不是 RDB 文件。这是因为 AOF 文件通常包含了更完整的操作记录,从而能够恢复更完整的数据状态。而 RDB 文件是定时生成的数据快照,所以它可能没有记录到最后一次快照之后发生的所有更改。因此,使用 AOF 文件恢复数据可以提供更高的数据完整性。
数据过期策略
1.惰性删除,在设置该key过期时间后,我们不去管它。当需要该key时,我们检查其是否过期。如果过期,我们就删掉它;反之,返回该key。
2.定期删除,每隔一段时间,我们就对一些key进行检查,并删除里面过期的key。定期清理的两种模式是:1) SLOW模式,是定时任务,执行频率默认为10hz,每次不超过25ms,可以通过修改配置文件redis.conf的hz选项来调整这个次数;2) FAST模式,执行频率不固定,每次事件循环会尝试执行,但两次间隔不低于2ms,每次耗时不超过1ms。
Redis的过期删除策略是:惰性删除 + 定期删除两种策略配合使用。
假如缓存过多,内存被占满了怎么办?
默认是noeviction,不删除任何数据,内部不足时直接报错。这个可以在redis的配置文件中进行设置。
里面有两个非常重要的概念:一个是LRU,另外一个是LFU。LRU的意思就是最少最近使用。它会用当前时间减去最后一次访问时间。这个值越大,则淘汰优先级越高。LFU的意思是最少频率使用。它会统计每个key的访问频率。值越小,淘汰优先级越高。我们在项目中设置的是allkeys-lru,它会挑选最近最少使用的数据进行淘汰,把一些经常访问的key留在redis中。
Redis分布式锁如何实现?
在redis中提供了一个命令SETNX
(SET if not exists)。由于redis是单线程的,用了这个命令之后,只能有一个客户端对某一个key设置值。在没有过期或删除key的时候,其他客户端是不能设置这个key的。
redis的SETNX
指令不好控制这个问题。我们当时采用的是redis的一个框架Redisson实现的(底层是setnx和lua脚本)保证原子性。在Redisson中需要手动加锁,并且可以控制锁的失效时间和等待时间。当锁住的一个业务还没有执行完成的时候,Redisson会引入一个看门狗机制。就是说,每隔一段时间就检查当前业务是否还持有锁。如果持有,就增加加锁的持有时间。当业务执行完成之后,需要使用释放锁就可以了。还有一个好处就是,在高并发下,一个业务有可能会执行很快。客户1持有锁的时候,客户2来了以后并不会马上被拒绝。它会自旋不断尝试获取锁。如果客户1释放之后,客户2就可以马上持有锁,性能也得到了提升。
是可以重入的。这样做是为了避免死锁的产生。这个重入其实在内部就是判断是否是当前线程持有的锁,如果是当前线程持有的锁就会计数,如果释放锁就会在计数上减一。在存储数据的时候采用的hash结构,大key可以按照自己的业务进行定制,其中小key是当前线程的唯一标识,value是当前线程重入的次数。
这个是不能的。比如,当线程1加锁成功后,master节点数据会异步复制到slave节点,此时如果当前持有Redis锁的master节点宕机,slave节点被提升为新的master节点,假如现在来了一个线程2,再次加锁,会在新的master节点上加锁成功,这个时候就会出现两个节点同时持有一把锁的问题。
我们可以利用Redisson提供的红锁来解决这个问题,它的主要作用是,不能只在一个Redis实例上创建锁,应该是在多个Redis实例上创建锁,并且要求在大多数Redis节点上都成功创建锁,红锁中要求是Redis的节点数量要过半。这样就能避免线程1加锁成功后master节点宕机导致线程2成功加锁到新的master节点上的问题了。
但是,如果使用了红锁,因为需要同时在多个节点上都添加锁,性能就变得非常低,并且运维维护成本也非常高,所以,我们一般在项目中也不会直接使用红锁,并且官方也暂时废弃了这个红锁。
Redis本身就是支持高可用的,要做到强一致性,就非常影响性能,所以,如果有强一致性要求高的业务,建议使用ZooKeeper实现的分布式锁,它是可以保证强一致性的。
Redis集群方案?
主从复制
主从集群,实现读写分离。一般都是一主多从,主节点负责写数据,从节点负责读数据,主节点写入数据之后,需要把数据同步到从节点中。
主从同步分为了两个阶段,一个是全量同步,一个是增量同步。
全量同步是指从节点第一次与主节点建立连接的时候使用全量同步,流程是这样的:
第一:从节点请求主节点同步数据,其中从节点会携带自己的replication id和offset偏移量。
第二:主节点判断是否是第一次请求,主要判断的依据就是,主节点与从节点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己的replication id和offset发送给从节点,让从节点与主节点的信息保持一致。
第三:在同时主节点会执行BGSAVE
,生成RDB文件后,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的RDB文件,这样就保持了一致。
当然,如果在RDB生成执行期间,依然有请求到了主节点,而主节点会以命令的方式记录到日志文件,最后把这个日志文件发送给从节点,这样就能保证主节点与从节点完全一致了,后期再同步数据的时候,都是依赖于这个日志文件,这个就是全量同步。
增量同步指的是,当从节点服务重启之后,数据就不一致了,所以这个时候,从节点会请求主节点同步数据,主节点还是判断不是第一次请求,不是第一次就获取从节点的offset值,然后主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步。
哨兵模式
保证了redis集群的高可用性,实现主从集群的自动故障恢复
首先可以搭建主从集群,再加上使用Redis中的哨兵模式,哨兵模式可以实现主从集群的自动故障恢复,里面就包含了对主从服务的监控、自动故障恢复、通知;如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主;同时Sentinel也充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端,所以一般项目都会采用哨兵的模式来保证Redis的高并发高可用。
由于Redis master节点和Redis slave节点和Sentinel处于不同的网络分区,使得Sentinel没有能够心跳感知到master,所以通过选举的方式提升了一个slave为master,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在old master那里写入数据,新节点无法同步数据,当网络恢复后,Sentinel会将old master降为slave,这时再从新master同步数据,这会导致old master中的大量数据丢失。
关于解决的话,我记得在Redis的配置中可以设置:第一可以设置最少的slave节点个数,比如设置至少要有一个从节点才能同步数据,第二个可以设置主从数据复制和同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失。
Redis分片集群有什么作用,其中如何存储和读取的?
分片集群主要解决的是海量数据存储的问题,集群中有多个master,每个master保存不同数据,并且还可以给每个master设置多个slave节点,就可以继续增大集群的高并发能力。同时每个master之间通过ping监测彼此健康状态,就类似于哨兵模式了。当客户端请求可以访问集群任意节点,最终都会被转发到正确节点。
Redis 集群引入了哈希槽的概念,有 16384 个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围,key通过CRC16校验后对16384取模来决定放置哪个槽,通过槽找到对应的节点进行存储。
java
h红红火火恍恍惚惚
Hello World
Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.
Quick Start
Create a new post
1 | $ hexo new "My New Post" |
More info: Writing
Run server
1 | $ hexo server |
More info: Server
Generate static files
1 | $ hexo generate |
More info: Generating
Deploy to remote sites
1 | $ hexo deploy |
More info: Deployment