嗨~我是可拟雀,一个后端开发工程师,毕业于某985大学,目前供职于bat某大厂核心部门后端。每天分享最新面经答案,希望在大环境不好的当下能帮到你,让你多积累面试经验。需要内推或者面经合集请评论哦。1.介绍一下redisson分布式锁?答:Redisson是一个基于Redis实现的Java分布式对象存储和缓存框架,它提供了丰富的分布式数据结构和服务,如分布式锁、分布式队列、分布式Rate Limiter等。其中,Redisson的分布式锁是一种重要的功能,主要用于在分布式系统中实现互斥性,保证在任意时刻只有一个线程能够持有锁。在Redisson中,使用key来作为是否上锁的标志。当通过getLock(String key)方法获得相应的锁之后,这个key即作为一个锁存储到Redis集群中。在接下来如果有其他的线程尝试获取名为key的锁时,便会向集群中进行查询。如果能够查到这个锁并发现相应的value的值不为0,则表示已经有其他线程申请了这个锁同时还没有释放,则当前线程进入阻塞;否则由当前线程获取这个锁并将value值加一。如果是可重入锁的话,则当前线程每获得一个自身线程的锁,就将value的值加一,而每释放一个锁则将value值减一,直到减至0,完全释放这个锁。Redisson分布式锁具有一些重要的特性。首先,它支持锁的超时时间,这意味着如果一个线程在持锁期间挂掉了而没主动释放锁,那么通过超时时间可以保证该线程在超时后可以释放锁,这样其他线程才可以继续获取锁。其次,Redisson提供了可重入锁、公平锁等常用的分布式锁,还支持异步执行、锁的自动续期、锁的等待等特性。此外,由于Redis是缓存型数据库,拥有很高的性能,因此Redisson的加锁和释放锁开销较小,并且能够很轻易地实现分布式锁。在使用上,Spring Boot项目可以方便地整合Redisson,通过添加Redisson依赖和配置RedissonClient客户端,就可以在项目中使用Redisson的分布式锁功能。2.redission分布式锁底层如何实现答:Redisson分布式锁底层实现主要依赖于Redis的特性和命令,结合Redisson的客户端库进行封装和优化。以下是Redisson分布式锁底层实现的大致原理:Redis数据结构选择:Redisson的分布式锁主要使用Redis的字符串(String)数据结构来存储锁的信息。每个锁在Redis中对应一个特定的key,该key的值用来表示锁的状态。加锁机制:当客户端尝试获取锁时,Redisson会向Redis发送一个SET命令,这个命令会尝试将锁对应的key设置为一个随机的UUID值,并设置过期时间(TTL)。同时,这个SET命令会带上NX(如果key不存在才设置)和PX(设置key的过期时间,单位为毫秒)参数。如果SET命令执行成功,表示客户端成功获取到了锁。这个UUID值作为锁的持有者标识,用于后续的解锁操作。如果SET命令执行失败(即key已经存在),说明锁已经被其他客户端持有,当前客户端需要等待或者返回失败。锁的可重入性:Redisson支持可重入锁,即同一个线程可以多次获取同一个锁而不会产生死锁。在Redisson中,当一个线程多次获取同一个锁时,它会在Redis中对应的key的value上增加重入次数,每次释放锁时,重入次数减一,直到重入次数为0时,才真正释放锁。锁的自动续期:为了避免客户端在持有锁期间因为网络问题或GC等原因导致与Redis服务器的连接断开,Redisson提供了锁的自动续期机制。当一个客户端获取到锁后,它会启动一个后台线程,定时向Redis发送命令延长锁的过期时间。解锁机制:当客户端需要释放锁时,它会向Redis发送一个Lua脚本,这个脚本会检查当前客户端是否持有锁(即比较UUID值),并删除对应的key,从而实现解锁。如果客户端在持有锁期间崩溃,由于设置了锁的过期时间,锁最终会自动释放,避免死锁。公平锁实现:Redisson也支持公平锁,它通过Redis的有序集合(Sorted Set)数据结构来实现。当多个客户端请求锁时,它们会在有序集合中按照请求的顺序排队,Redis会根据集合中元素的顺序来决定哪个客户端能够获取到锁。分布式环境下的安全性:Redisson的分布式锁实现考虑了分布式环境下的安全性问题。例如,它使用UUID作为锁的持有者标识,确保每个锁只能被一个客户端持有;同时,通过Lua脚本原子性地执行加锁和解锁操作,避免在并发环境下出现竞态条件。3.基于setnx实现的分布式锁的缺点有哪些?答:基于SETNX实现的分布式锁虽然是一种常用的解决方案,但也存在一些明显的缺点。以下是这些缺点的主要方面:单点风险:SETNX实现的分布式锁存在单点风险。如果存储分布式锁的Redis节点(或称为key)挂掉,就可能发生丢锁的情况。一旦丢锁,就可能导致多个客户端同时持有锁,从而使得分布式锁失效。锁释放问题:SETNX本身只提供了加锁的机制,对于锁的释放通常依赖于额外的逻辑。例如,一个常见的做法是在业务逻辑完成后使用DEL命令删除锁。然而,如果持有锁的客户端在释放锁之前崩溃或网络中断,那么这个锁就可能永远不会被释放,造成死锁。此外,如果锁的释放逻辑没有正确实现,也可能导致锁的误释放或重复释放。不可重入性:基于SETNX实现的分布式锁通常不具有可重入性。可重入锁允许同一个线程多次获取同一把锁,而不会造成死锁。然而,使用SETNX实现的锁通常不支持这种特性,可能导致同一线程在多次获取锁时发生冲突。性能问题:在高并发场景下,基于SETNX实现的分布式锁可能会成为性能瓶颈。由于每次加锁和解锁都需要与Redis进行网络通信,这可能成为系统的瓶颈,尤其是在锁竞争激烈的情况下。缺乏公平性:SETNX实现的分布式锁通常不提供公平性保证。公平性意味着等待时间最长的客户端应该优先获得锁。然而,基于SETNX的实现通常是基于先到先得的原则,可能导致某些客户端长时间无法获得锁。4.基于redis如何实现可重入的分布式锁答:基于Redis实现可重入的分布式锁,需要确保同一个线程或进程能够多次获取同一把锁而不会造成死锁,并且在释放锁时能够正确计数和解锁。以下是一个基于Redis实现可重入分布式锁的基本思路:锁的数据结构:使用Redis的哈希结构(Hash)来存储锁的信息。每个锁对应一个哈希表,哈希表的key是锁的标识(例如锁的名称),哈希表的value是一个包含多个字段的映射,其中至少包含以下字段:lockCount:表示当前锁被持有的次数(可重入次数)。ownerId:表示当前持有锁的客户端的唯一标识(例如线程ID或进程ID)。expireTime:表示锁的过期时间,用于防止客户端崩溃导致的死锁。加锁:客户端尝试获取锁时,首先检查Redis中是否存在对应的哈希表。如果不存在,则创建一个新的哈希表,并设置lockCount为1,ownerId为当前客户端的唯一标识,并设置一个合理的expireTime。如果已经存在,则检查ownerId是否与当前客户端的唯一标识相同。如果相同,表示当前客户端已经持有该锁,将lockCount加1。如果ownerId不同,表示锁被其他客户端持有,当前客户端需要等待或返回失败。锁的重入:当同一个客户端多次尝试获取同一把锁时,只需要检查ownerId是否匹配,并增加lockCount的值即可。5.项目的qps大概多少6.如何自己实现一个限流算法中的滑动窗口答:滑动窗口限流算法的思路在于维护一个时间窗口,并跟踪这个窗口内的请求数量。当新的请求到达时,算***检查当前窗口内的请求数是否已经达到限制。如果没有达到限制,则允许该请求,并将其添加到窗口中;如果已经达到限制,则拒绝该请求。随着时间的推移,窗口会向前滑动,旧的请求会逐渐移出窗口,从而为新的请求腾出空间。以下是实现滑动窗口限流算法的基本思路:定义窗口大小和请求限制:首先,确定滑动窗口的大小(例如,每秒钟、每分钟或每小时)以及窗口内允许的最大请求数。维护请求队列和时间戳:使用一个队列(如Python的deque)来存储窗口内的请求时间戳。当请求到达时,将其时间戳添加到队列中。检查并移除过期请求:在每次判断请求是否允许之前,检查队列中的最早请求是否已过期(即其时间戳是否超出了窗口的边界)。如果是,则将其从队列中移除。计算当前窗口内的请求数:队列中剩余的元素数量即代表了当前窗口内的请求数。判断请求是否允许:如果当前窗口内的请求数未达到限制,允许新请求,并将其时间戳添加到队列中;如果已达到限制,则拒绝新请求。处理窗口滑动:随着时间的推移,窗口会自动向前滑动,因为过期的请求会被移除。新的请求会填充到窗口的末尾。8.code:用两个队列实现一个栈,java语言9.jvm内存结构答:堆(Heap):这是Java虚拟机中的一块内存区域,所有线程共享。它主要用于存储对象实例和数组。堆被划分为年轻代、老年代和永久代(在JDK8中取消了永久代)。年轻代又被划分为Eden区、Survivor区(含:S0和S1)。默认情况下,年轻代和老年代的比例为1:2,即年轻代占整个堆空间的1/3,老年代占整个堆空间的2/3。方法区(Method Area):这也是线程共享的内存区域,主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虚拟机栈(VM Stack):每个线程运行时所需要的内存称为虚拟机栈。每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存,用于存储局部变量表、操作数栈、常量池引用等信息。程序计数器(Program Counter Register):用于记录下一条JVM指令的执行地址(如果正在执行的是本地方法则为空)。每一个线程都有一个程序计数器,当CPU因为时间片轮转等原因切换线程的时候,能保存当前线程的执行进度。同时,程序计数器不会存在内存溢出。本地方法栈(Native Method Stack):这也是线程私有的,用于支持native方法的执行。10.本地方法栈和虚拟机栈的区别答:本地方法栈(Native Method Stacks)与虚拟机栈(VM Stack)在Java虚拟机中各自扮演着重要的角色,但它们之间存在一些显著的区别。首先,两者的服务对象不同。虚拟机栈主要为Java方法(也就是字节码)服务,主管Java程序运行,保存方法的局部变量、部分结果,并参与方法的调用和返回。而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。本地方法是用其他语言(如C、C++)编写的方法,通过Java Native Interface(JNI)调用。因此,本地方法栈的存在主要是为了支持本地方法的调用。其次,两者在结构上也有所不同。虚拟机栈是每个线程创建时都创建的,内部保存的单位是栈帧,对应一次次方法调用,其生命周期与线程一致。而本地方法栈与虚拟机栈一样,也是线程私有的,每个线程都有一个本地方法栈。这意味着每个线程都有自己独立的本地方法栈和虚拟机栈,它们之间进行独立的分配和释放。最后,两者在异常处理上也有所区别。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时抛出异常。具体来说,当线程请求的栈容量超过固定大小时,会抛出StackOverflowError异常;而当没有足够的内存进行动态拓展时,会抛出OutOfMemoryError错误。11.类加载的过程答:类加载的过程在Java中是一个复杂且重要的机制,它确保了类的正确性和安全性。以下是类加载的详细过程:加载(Loading):加载阶段是类加载过程的起始点,主要由类加载器(ClassLoader)完成。类加载器通过类的全限定名查找并读取定义此类的字节码文件(.class文件)。这些字节码文件通常来自文件系统、网络或其他来源。读取到的字节码数据被加载到内存中,并且JVM会为此类创建一个Class对象,作为方法区中这个类的各种数据的访问入口。链接(Linking):链接阶段包括验证、准备和解析三个子阶段。验证(Verification):对加载的类的字节码进行校验,以确保其符合Java虚拟机规范。这个校验过程包括结构验证、语义验证、字节码验证和符号引用验证等,旨在确保类的正确性、安全性和一致性。准备(Preparation):为类的静态变量分配内存空间,并设置初始值。这些静态变量是类级别的变量,而非实例级别的变量。解析(Resolution):将常量池内的符号引用转换为直接引用的过程。这涉及到对其他类或接口的符号引用的解析,以确保它们可以被正确地引用和使用。初始化(Initialization):初始化阶段是类加载过程的最后阶段,在这个阶段中才真正开始执行类中定义的Java代码。如果类中有静态初始化块或静态变量赋值操作,那么这些操作将在初始化阶段被执行。JVM会保证一个类的<clinit>()方法在多线程环境中被正确地加锁和同步,确保类的初始化只会被执行一次。需要注意的是,类加载的过程并不是在程序一启动时就立即完成的,而是遵循“什么时候初始化”的原则进行加载。只有在首次主动使用某个类的时候(如创建类的实例、访问类的静态变量或静态方法、反射调用等),该类才会被初始化。12.redis常见数据结构(常见13.zset的底层数据结构(散列+跳表14.redis缓存穿透的原因和解决办法答:原因缓存穿透是指查询一个不存在的数据,由于缓存中也没有该数据,因此每次查询都会穿透到数据库层,从而给数据库带来压力。这种情况通常发生在恶意攻击或者系统bug时,大量查询不存在的数据,导致数据库负载过高。解决办法:布隆过滤器:布隆过滤器是一种空间效率极高的概率型数据结构,它利用多个哈希函数将元素映射到位图中。当查询一个元素时,可以通过检查对应的位图位置来判断元素是否存在。虽然布隆过滤器存在误判的可能性,即可能将不存在的元素误判为存在,但可以有效地过滤掉大部分不存在的元素,从而降低缓存穿透的概率。缓存空对象:对于不存在的数据,可以在缓存中存储一个空对象或者特殊标记,并设置较短的过期时间。这样,当再次查询这个不存在的数据时,可以直接从缓存中获取空对象或特殊标记,从而避免了对数据库的查询。但需要注意的是,这种方法可能会增加缓存的存储成本,并且如果大量查询不存在的数据,仍然可能给缓存带来压力。数据校验与过滤:在应用层增加数据校验逻辑,对于非法或不符合规范的请求进行过滤。这可以减少恶意请求对缓存和数据库的冲击。限流与熔断:通过限流策略限制对数据库的查询频率,当达到一定的阈值时,可以暂时熔断对数据库的查询,从而保护数据库免受过大的压力。这可以确保在高并发场景下,数据库不会因为过多的无效查询而崩溃。15.mysql索引底层数据结构 (b+树16.mysql对两个不同字段建立索引有几棵b+树答:在MySQL中,对于每一个独立的索引,都会有一棵B+树与之对应。所以,如果你对两个不同的字段分别建立了索引,那么你会有两棵B+树。每棵B+树都是根据该索引字段的值进行排序的。例如,假设你有一个表my_table,并且你对字段field1和field2分别建立了索引,那么:对于field1的索引,MySQL会维护一棵B+树,其中的节点按照field1的值进行排序。对于field2的索引,MySQL会维护另一棵B+树,其中的节点按照field2的值进行排序。这两棵B+树是独立的,互不干扰。当你根据field1进行查询时,MySQL会使用第一棵B+树;当你根据field2进行查询时,MySQL会使用第二棵B+树。17.mysql联合索引的最左前缀原理?答:MySQL的联合索引(也称为复合索引或多列索引)的最左前缀原理是指,当MySQL使用联合索引进行查询时,它会从联合索引的最左列开始匹配查询条件。只有当查询条件使用了联合索引的最左侧列时,索引才会被有效利用。具体来说,假设我们有一个联合索引 (a, b, c),这个索引是按照列 a、b 和 c 的顺序进行排序的。现在,我们考虑以下几种查询情况:单独查询列a:在这种情况下,查询条件使用了联合索引的最左列(列a),因此MySQL可以直接使用这个索引来快速定位到匹配的行。查询列a和列b:查询条件同时包含了列a和列b,这也满足了最左前缀原则,因为列a是联合索引的最左列。MySQL会利用这个联合索引来加速查询。查询列a、列b和列c:当查询条件包含了联合索引的所有列(列a、列b和列c)时,索引的使用效率是最高的,因为所有列都被用来定位数据。然而,以下情况则不会有效地利用联合索引 (a, b, c):只查询列b或列c:由于列b和列c不是联合索引的最左列,因此当查询条件只包含这些列时,联合索引不会被使用。MySQL会进行全表扫描或者尝试使用其他可能的索引(如果有的话)。最左前缀原则也适用于索引的列顺序。如果你有一个 (b, a, c) 的联合索引,那么它对于基于列 b 的查询是高效的,但对于基于列 a 的查询则不是,因为列 a 不是这个联合索引的最左列。18.col1=, col2 between可以用这个联合索引吗19.为什么不等于条件会导致索引失效20.你实习想做什么反问:部分与业务日常吗项目实习,类似日常两个队列咋实现一个栈哇 给我整不会了lc原题,有空做一下q1,q2,push x就往q1加x,然后把q2按顺序全部接到q1后面,然把q1,q2的指针交换, pop就从q2 poll一个原文传送门
点赞 31
评论 5
全部评论

相关推荐

也许是天气_:实习这块全是假大空像AI生成的,没有实际内容。要体现出难点、亮点、解决问题的过程
点赞 评论 收藏
分享
头像
08-13 14:20
已编辑
门头沟学院 Java
之前在学校的时候,舍友老是熬夜打游戏,周末还喜欢早起打游戏😅,吵得没法睡到自然醒现在出来实习独居后,想干嘛就干嘛,打游戏刷视频,没有任何顾虑,学习工作,也没有人能打扰我🦌就这个独居爽
天才无敌小土豆:之前在学校 宿舍一个巨瘦的哥们天天熬夜打游戏 呼噜声还巨大 我睡觉超级敏感 天天睡不着 我睡他下铺 半夜踹他床板让他飞起来 就那一会不会打呼噜 然后继续 那段时间我感觉我都yw了 后来我换了个远一点的床铺 买了新的那种可以捏小的耳塞 老子睡觉爽死了 后悔大三才发现这种耳塞 老子yw又好了 天天夜里上厕所都梆硬
独居后,你的生活是更好了...
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务