Java八股文完整版:高频考点全覆盖

111 阅读10分钟
问:对缓存击穿,缓存雪崩,缓存穿透有了解过吗?说说者三个缓存问题的解决方案吧。

1.[缓存击穿]:某个热点key设置了过期时间,在高并发查询的情况下,该热点key过期了,导致大量的请求去访问数据库,最终压垮数据库。

解决方案:

(1)不给热点key设置过期时间(在redis中设置的过期时间都是逻辑过期时间,通过逻辑字段来判断key是否过期)。

(2)使用分布式锁,保证每次只有一个请求去访问数据库,在每次访问数据库前再做一次查询缓存的操作,然后获取锁并去数据库查询,将查到的数据重新缓存起来,下一次请求就会在缓存中查询到数据,就不会查询数据库,防止压垮数据库。

2.缓存雪崩:第一种情况:大量的key设置了相同的过期时间,在同一大量的key失效,导致大量的请求去访问数据库,导致压垮数据库。第二种情况:redis宕机。

解决方案:

  (1) 错开key的过期时间(TTL):在每个key的统一过期时间上在随机加上1~5分钟的过期时间。

  (2)设置服务降级,服务熔断,服务限流,到达阈值的时候直接返回自定义的错误信息。(作为系统的兜底策略)

  (3)为redis搭建集群,就包括哨兵模式,集群模式。

   (4)做二级缓存。(可以重点介绍,就引导面试官到运单信息查询模块,给他介绍二级缓存的实现还有缓存同步的问题,主要讲caffeine缓存同步的问题)

 3.缓存穿透:查询的key在缓存和数据库中都不存在,每次都会进入数据库查询,当大量的这种key访问数据库,就会导致缓存穿透。(这种情况多半上是恶意攻击)

解决方案:

  (1)在缓存中储存空值:当在数据库中没有查到数据时,将key关联null的键值对存储到缓存中,后续就会走缓存,这种解决方案缺点很明显,就是存储大量无用的存储,浪费空间。

  (2)使用布隆过滤器:在查询缓存的时候先去布隆过滤器中查询是否存在缓存,再去查询缓存。

这份小册是从基础到高级涵盖了足足30个技术栈的,包含了JAVA基础,JAVA集合,JAVA并发,Spring,微服务,Netty,计算机网络,MQ,Zookeeper,Redis,MySQL,数据结构与算法以及设计模式等等,足足200余页,由于掘金篇幅限制我在这里就只展示部分内容了,扫一扫免费获取

image.png

问:具体讲讲你对这个布隆过滤器理解?

布隆过滤器类似hash表叫做bitmap(位图),在没有位置存放的是二进制的数据,1表示存在,0表示不存在。通过哈希函数计算储存位置。在后续查询缓存前先去布隆过滤器中查询数据是否存在,如果存在才去查缓存,如果不存在就直接返回空。

布隆过滤器的优点就是:储存二进制的数据,而非真实的数据,查询速度快。缺点:判断数据是否存在有误判率,不能做删除操作。

为了降低布隆过滤器的误判率,因为两个数据的hashCode可能相同,所以我们可以设置多哈希函数操作,较低过滤器的误判率。 

我们主要是根据redisson设置布隆过滤器,可以设置其误判率。在高并发的场景下,一般误判率控制在5%之内就可以了。

问:说说分布式锁方面的使用

在项目的支付模块中的扫码支付为了保证交易单的状态不会被除当前业务的其他业务修改,计算运力,我们都会使用分布式锁。

问:你能简单的讲述一下分布式锁的实现吗?(引导面试官到你的项目中,去解释)

分布式锁

可以跟面试官说说优惠劵超卖的问题。

在redisson中的获取锁方法底层主要是通过Lua(能够调用[redis命令],保证命令执行的原子性)实现的,如果获取锁方法没有设置过期时间,则分布式锁会有watch dog来保证延长锁的有效时间。

还可以通过setnx来实现分布式锁(因为redis是单线程的)。 通过 set If not exists并设置过期时间实现分布式锁,但是存在的问题就是无法确定要给锁设置多长的有效时间,所以在项目中比较少使用。

公平锁 (我们项目中使用的分布式锁,保证可重入性)

公平锁主要还是基于redisson实现的。锁的结构采用的是hash结构,以大小key的形式,在我业务中,使用订单id和交易单id进行拼接作为大key,使用当前的线程id作为小key,vlaue存放的就是上锁的次数。在业务修改交易单状态是需要先判断当前交易单是否被上锁(通过订单id,交易单id,进程id来获取对应的锁),如果被上锁,则当前线程的其他业务不能进行操作。保证订单状态的准确性。(判断幂等性) 保证可重入性,根据当前线程id来获取分布式锁,解决死锁的情况(分布式锁的底层实现)。

问:分布式锁可以实现重入吗?
重入的意思就是:在同一个线程中获取锁后继续获取锁。因为我们分布式锁中小key使用的是线程id,value就是上锁的次数,我们每次进行重入时就是value值+1,在释放锁的时候,就将对应的value-1,在value为0的时候就删除对应的信息。其他微服务就可以获取此分布式锁了。通过线程id的不同来保证分布式锁的互斥。(蓝字选答)

问:redisson实现的分布式锁,可以解决主从一致性的问题吗?

不能解决主从一致的问题,单master节点获取锁后就没释放锁就宕机了,此时slave节点变成了master节点,因为新的master没有上锁,所以新的master会进行上锁,破坏了锁的互斥性。为了解决这个问题我们可以使用红锁,也就是给一般以上的节点添加分布式锁,但是这样效率就变的很低,为了提高效率,建议采用zookeeper实现分布式锁。(听说过zookeeper实现分布式锁)

问:在集群的项目中为什么不能用关键字synchronized呢?

在两个微服务中,如果使用synchronized是无法达到同步上锁效果,因为两个微服务是两个单独的JVM。

问:了解过双写一致性吗?

双写一致性:当数据库中的数据发生修改的时候,我们需要修改缓存中的数据,保证数据库和缓存中的信息相同。

5ca96488062247719ff154f01f561e60.png在读操作的时候,会先到缓存中查询数据,如果没有命中的话就到数据库中查询。

在写操作的时候,会采用延迟双删。

问:在延迟双删中为什么要延迟删除缓存?

数据库采用的是主从模式,遵循读写分离,需要一些时间来将主数据库中的数据同步到从数据库,但是还是可能出现脏读的情况。

问:在延迟双删中为什么要删除两次缓存呢?

如果只有一次删除缓存操作的话就会有两种情况。

1.情况1:先删除缓存,再修改数据库。线程1删除缓存。在线程1要修改数据库前。线程2去做查询操作,发现没有缓存,就去数据库中查询数据并做缓存,之后线程1修改了数据库中的信息,最终出现缓存数据和数据库数据不相同的情况。(当前线程在删除缓存并且在写数据库前其他线程查询了就的数据)

693bbd6d70eb420a98936a47ca6f22a5.png

2.情况2:先修改数据,再删除缓存。当前缓存中方没有数据,线程1做查询操作,准备将旧数据缓存前,线程2做了修改数据和删除缓存的操作(此时是没有缓存的),最终缓存中就存储了旧的数据,出现脏读的情况。(当前线程要将旧的数据进行缓存的过程中进行了写数据库和删除缓存的操作)

8e1a0a3cb22e438181c8d97f6392f978.png

为了防脏读的缓存被使用,所以在数据同步的需要两次删除缓存的操作。 

问:延迟双删是没法保证强一致性的,有什么强一致性的方法吗?

方法一:使用分布式锁,每次只有一个线程进行操作,效率比较低下。

ba2085dde6604859a7bbb375c70ded99.png

方法二:因为缓存中的数据大多是读多写少,所以我们可以使用在读取数据的时候使用共享锁, 多个线程同时可以读取缓存但是其他线程不可以写,在修改数据的时候使用排他锁,会阻塞其他线程的读写操作。

541579dc2ee34fc0ba716a1f2d2c707b.png

排他锁和共享锁可以使用redisson实现,通过redissonClient获取对应的读写锁。 

1.读锁也就是共享锁。

edc516479ef34213be8d1f04c0a7a866.png

2.写锁也就是排他锁。

ebc5db7353b544bdb5de54f8a83ddbd5.png

必须保证读写锁的名字相同。

问:那有了解过最终一致性的方法吗?

在我们支付模块中,每次支付都需要获取支付模板也就是支付宝支付和微信支付模板,这些数据我们基本上不会修改,会将其缓存起来,在修改mysql中模板的数据的时候,通过rabbitmq发送消息,异步修改缓存的数据,达到最终一致的效果。

为什么mq可以实现最终一致的效果呢?

mq中的消息都是按照顺序进行消费的,消息的消费是和事务绑定的,如果事务进行了回滚操作则被消费的消息也会被重新放回队列的原先位置中,并且接收到消息的服务会异步进行处理。

28a04c5862c84335bd860660c0e7686e.png

当然我们不仅仅可以使用rabbitmq实现最终一致的效果,我们还可以使用canal实现最终一致的效果。canal主要是通过mysql的主从同步实现的。通过监听bin Log日志的方式来修改缓存中的信息,达到最终一致的效果。(binLog日志主要是储存DDL(数据定义语句)和DML(数据操纵语句))

ac1d3f9e587d456dbee262d98f3bd09c.png

问:redis做为缓存,数据的持久化是怎么做的呢?

在持久化上用 RDB和AOF两种方式。

问:说说你对RDB和AOF的理解吧。

1.RDB:通过对数据做快照的方式做持久化,将快照存放到磁盘上,后续需要恢复数据的时间就使用该快照进行恢复。

RDB的执行原理:在主进程会有一个页表文件(用于映射数据在物理内存上的数据),通过复制该页表到子进程中,子进程通过页表找到数据并做快照。

当是如果在修改数据的过程中做RDB就会出现脏读的情况,RDB通过设置数据为只读,在修改数据的时候复制一份相同的数据进行修改如何修改页表的映射,最终解决脏读的问题。

e94e014ae1274110a34c633995e71827.png

这份小册是从基础到高级涵盖了足足30个技术栈的,包含了JAVA基础,JAVA集合,JAVA并发,Spring,微服务,Netty,计算机网络,MQ,Zookeeper,Redis,MySQL,数据结构与算法以及设计模式等等,足足200余页,由于掘金篇幅限制我在这里就只展示部分内容了,扫一扫免费获取

image.png