【面经】快手日常实习后端开发岗

一 synchronized 和 ReentrantLock 区别是什么?

1.两者都是可重入锁

        可重入锁,也叫做递归锁,指的是在一个线程中可以多次获取同一把锁,比如 一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁, 两者都是同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

2.synchronized 依赖于 JVM ,而 ReentrantLock 依赖于 API

  • synchronized 是依赖于 JVM 实现的

  • ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成)

3.ReentrantLock 比 synchronized 增加了一些高级功能

        相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)

  • 等待可中断,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。

  • ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。

  • ReentrantLock类线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”

4.使用选择

  • 除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。

  • synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放

二 redis持久化机制

        为了能够重用Redis数据,或者防止系统故障,我们需要将Redis中的数据写入到磁盘空间中,即持久化。Redis提供了两种不同的持久化方法可以将数据存储在磁盘中,一种叫快照RDB,另一种叫只追加文件AOF

1.RDB

        在指定的时间间隔内将内存中的数据集快照写入磁盘(Snapshot),它恢复时是将快照文件直接读到内存里。

优势:适合大规模的数据恢复;对数据完整性和一致性要求不高

劣势:在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

2.AOF

        以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

        AOF采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时, Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.。

优势:

每修改同步:appendfsync always 同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好

每秒同步:appendfsync everysec 异步操作,每秒记录,如果一秒内宕机,有数据丢失

不同步:appendfsync no 从不同步

劣势:

相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb

aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同

三 redis的策略淘汰

        Redis会不断的删除一些过期数据,但是很多没有设置过期时间的数据也会越来越多,那么Redis内存不够用的时候是怎么处理的呢?答案就是淘汰策略。当Redis的内存超过最大允许的内存之后,Redis会触发内存淘汰策略,删除一些不常用的数据,以保证Redis服务器的正常运行。

Redisv4.0前提供 6种数据淘汰策略

  • volatile-lru:利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used )

  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)

  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

  • no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。

Redisv4.0后增加以下两种

  • volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰(LFU(Least Frequently Used)算法,也就是最频繁被访问的数据将来最有可能被访问到)

  • allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key。

        内存淘汰策略可以通过配置文件来修改,Redis.conf对应的配置项是maxmemory-policy 修改对应的值就行,默认是noeviction。

四 HashMap的底层数据结构是什么?

        在JDK1.7 和JDK1.8 中有所差别。

        在JDK1.7 中,由“数组+链表”组成,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。

        在JDK1.8 中,由“数组+链表+红黑树”组成。当链表过长,则会严重影响 HashMap 的性能,红黑树搜索时间复杂度是 O(logn),而链表是 O(n)。因此,JDK1.8 对数据结构做了进一步的优化,引入了红黑树,链表和红黑树在达到一定条件会进行转换:

  • 当链表超过 8 且数据总量超过 64 才会转红黑树。

  • 将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树,以减少搜索时间。

五 线程安全的集合有哪些?线程不安全的呢?

1.线程安全的:

  • Hashtable:比HashMap多了个线程安全。

  • ConcurrentHashMap:是一种高效但是线程安全的集合。

  • Vector:比Arraylist多了个同步化机制。

  • Stack:栈,也是线程安全的,继承于Vector。

2.线性不安全的:

  • HashMap

  • Arraylist

  • LinkedList

  • HashSet

  • TreeSet

  • TreeMap

六 分布式事务

        分布式事务服务(Distributed Transaction Service,DTS)是一个分布式事务框架,用来保障在大规模分布式环境下事务的最终一致性。

        CAP理论告诉我们在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的,所以我们只能在一致性和可用性之间进行权衡。为了保障系统的可用性,互联网系统大多将强一致性需求转换成最终一致性的需求,并通过系统执行幂等性的保证,保证数据的最终一致性。

七 介绍下Redis Sentinel(哨兵)

        主从模式下,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这种方式并不推荐,实际生产中,我们优先考虑哨兵模式。这种模式下,master 宕机,哨兵会自动选举 master 并将其他的 slave 指向新的 master。

        Redis Sentinel是社区版本推出的原生高可用解决方案,其部署架构主要包括两部分:Redis Sentinel集群和Redis数据集群。其中Redis Sentinel集群是由若干Sentinel节点组成的分布式集群,可以实现故障发现、故障自动转移、配置中心和客户端通知。Redis Sentinel的节点数量要满足2n+1(n>=1)的奇数个。

优点:

  • Redis Sentinel集群部署简单;

  • 能够解决Redis主从模式下的高可用切换问题;

  • 很方便实现Redis数据节点的线形扩展,轻松突破Redis自身单线程瓶颈,可极大满足Redis大容量或高性能的业务需求;

  • 可以实现一套Sentinel监控一组Redis数据节点或多组数据节点。

缺点:

  • 部署相对Redis主从模式要复杂一些,原理理解更繁琐;

  • 资源浪费,Redis数据节点中slave节点作为备份节点不提供服务;

  • Redis Sentinel主要是针对Redis数据节点中的主节点的高可用切换,对Redis的数据节点做失败判定分为主观下线和客观下线两种,对于Redis的从节点有对节点做主观下线操作,并不执行故障转移。

  • 不能解决读写分离问题,实现起来相对复杂。

八 redis缓存三剑客

1.缓存穿透(Cache Penetration)

        指的是请求的数据在缓存和数据库中都不存在,从而绕过了缓存直接访问数据库,导致数据库压力增大。

解决方案

        使用布隆过滤器(Bloom Filter)来判断请求的数据是否存在。

        将无效的请求也存入缓存(设置较短的过期时间),避免重复查询数据库。

2.缓存雪崩(Cache Avalanche)

        指的是缓存中的大量数据同时过期,导致大量请求同时访问数据库,可能引发数据库负载过高。

解决方案

        设置缓存数据的过期时间时,使用随机值,使得不同数据的过期时间分散,避免集中过期。

        实现缓存预热策略,即在系统启动时或数据更新时,预先加载数据到缓存中。

        使用多级缓存,例如本地缓存加分布式缓存,降低数据库的压力。

3.缓存击穿(Cache Breakdown)

        指的是某个热点数据的缓存过期,导致大量请求同时到达数据库,可能使数据库瞬间压力增加。

解决方案

        使用互斥锁(mutex)机制,当某个热点数据的缓存过期时,只有一个请求能去加载数据,其他请求等待,避免重复查询数据库。

        使用缓存预热和后台异步更新策略,定期刷新热点数据的缓存。

九 MySQL 和 Redis 的双写一致性问题

1. 缓存延时双删策略

        首先更新数据库中的数据。删除与该数据相关的缓存,防止读取到旧数据。等待一小段时间后(如 500ms 到 1s),再次删除缓存,确保在这段时间内,若有其他读请求更新了缓存,也能再次清理掉它。

2. 读写操作加分布式锁

        在写 MySQL 数据库前,先获取一把分布式锁。锁定成功后,进行数据库写操作。在释放锁前,删除 Redis 中的缓存数据。在读请求到来时,如果发现缓存中没有数据,在从数据库读取数据前也加锁,确保写请求和读请求不会出现冲突。

3. 异步消息队列解决方案

        先更新 MySQL 数据库,再将数据库更新的操作发送到消息队列中。消息队列的消费者监听数据库的更新,接收到消息后,更新或删除 Redis 缓存。

4. 先删缓存再更新数据库

        先删除 Redis 中缓存的数据,随后更新 MySQL 中的数据库数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值