面试经验-2021.10.14

一、技术问题


1. 数组相关的问题

问题1:map相关问题
  1. 介绍下map:例如扩容原理,阈值,默认大小,数据结构
  2. map.put的原理 (这里说下并发下数据丢失问题,并把 concurrenthashmap这个对象引出来,说这个在指定的链表中加了锁,效率好很多等等)
  3. map.put怎么解决hash碰撞:碰撞的意思就是hash值相同,那么就会把这个元素放到数组中同一个位置,形成单向链表,如果链表长度大于8升级为红黑树,小于6降级为链表( https://www.jianshu.com/p/379680144004)
  4. 为什么使用红黑树:因为红黑树是平衡二叉树,需要遍历的层级少




2. 数据加密

问题1:有哪些加密算法
  • RSA(非对称加密)、AES(对称加密)、base64、md5等等



问题2:怎么保证数据安全性
  • RSA的加密过程如下: (1)A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。
    (2)A传递自己的公钥给B,B用A的公钥对消息进行加密。 (3)A接收到B加密的消息,利用A自己的私钥对消息进行解密。




3. 为什么用b+

大概说下b+树结构和回表操作
https://blog.csdn.net/weixin_38003389/article/details/86604309




4. 索引的注意事项

从表设计开始讲,1.合理的设计表,2.然后就讲索引,3.讲什么查询才能用到索引等等
https://www.cnblogs.com/heyonggang/p/6610526.html




5. 事务的传播机制

7种事务
https://blog.csdn.net/weixin_36162966/article/details/106575192
https://fangshixiang.blog.csdn.net/article/details/80445933




6. 分布式事务原理(seata)

  1. 简述:xa我们的业务场景有两个操作,一个减库存,一个生成订单,我们要保证这两个操作的幂等性,他们在不同的服务中,这时候我们可以加
    seata 的注解。
    他的执行原理是,会先生成一个全局的事务id叫xid,这个xid是一个标识,例如我们的减库存操作他这个服务会开启自己的本地事务,成功失败或者超时等等都会通知tc中心,Tc会记录各个服务是否成功,还有回滚语句,如果有任何一个服务失败,那么他就会去通知之前成功的服务,进行回滚操作。回滚时靠着自己保存的回滚语句进行回滚。如果全部成功,则删除记录的一些回滚信息,并返回成功

  2. 好处:相较于tcc,减少了两段式提交,对于整个流程都精简了许多,并且是无侵入的。

3. **注意事项**:在布式事务修改数据的时候,有全局锁和本地锁来处理分布式事务写隔离

https://blog.csdn.net/tianyaleixiaowu/article/details/95208906
https://www.cnblogs.com/zpKang/p/14197704.html




7. redis相关问题

1. 锁如何保证过期时间设置的原子性

解决:使用 setIfAbsent (spring中的方法)来执行原子操作,这个可以加过期时间,是原子性操作;还有就是使用lua脚本去加锁

https://blog.csdn.net/qq_37730370/article/details/107176729

2. redis锁是怎么实现的

就说下这个锁的一个获取流程

3. redis锁如何保证锁过期但是操作没执行完的情况

可以说依照义务来定,设置长一点的时间啊,或者使用redisson的工具类,它提供了看门狗相应的实现,可以动态延长这个过期时间,不过很耗性能,自己依照场景而定吧

4. redis锁你使用在哪些场景

两个点,缓存和锁
缓存:门店信息之类的,可能会存在下面说的不一致问题
锁:具体使用每个增加和修改的地方都加了锁,防止重复点击浪费资源,还有就是锁代码块,库存之类的修改操作,保证不超买超卖问题。

5. redis的缓存穿透、击穿、雪崩
穿透
  1. 场景:
            用户访问接口,发现缓存中没有数据,则去数据库中查询,发现数据库中也没有数据,则直接返回空。如果别人这样大量的访问,那么我们的缓存则没有起到保护数据库的作用

  2. 解决:

    • 设置空缓存,这样保证用户的查询不会每次都访问到数据库中的,缺点是比较浪费缓存空间
    • 使用布隆过滤器,它能够迅速判断一个元素是否在一个集合中缺点是有一定的误判性,误判率越低,则底层维护的数组越长,占用空间越大。因此,误判率实际取值,根据服务器所能够承受的负载来决定,不是拍脑袋瞎想的。(https://www.cnblogs.com/rinack/p/9712477.html)
击穿
  1. 场景:
            用户访问那些没有被缓存的数据,这个时候,又有大面积的访问请求过来了,导致数据库压力非常大。这里要区分两个场景,一个是热点数据,一个是非热点数据,非热点数据或许因为别人的带货,突然访问量变大,这个时候容易没有缓存。
  2. 解决:
    • 加redis锁,服务层面做限流,不至于因为一个接口导致整个服务崩溃
    • 热点数据设置为永久缓存
雪崩
  1. 场景:
            大量的key过期,所有请求都访问到数据库上面去了,击穿的加强版

  2. 解决:

  • 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
  • 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
  • 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

相关文章:https://www.cnblogs.com/windpoplar/p/10923830.html

6. redis是单线程的嘛
  1. redis的工作线程一直是单线程的
  2. 在redis 6.x的时候,io升级为了多线程,但是工作线程还是只有一个。
  3. 意思就是我们发送指定到redis里面的时候是多线程的操作,但是执行命令的时候还是原子性的执行,其他未执行的任务会被放到队列中等待被执行。
7. redis是怎么删除过期缓存

https://blog.csdn.net/yuanlong122716/article/details/104420880

  1. 定期删除+惰性删除
  2. 定期删除是指Redis默认每隔 100ms 就 随机抽取 一些设置了过期时间的key,检测这些key是否过期,如果过期了就将其删除。
  3. 惰性删除不是去主动删除,而是在你要获取某个key 的时候,redis会先去检测一下这个key是否已经过期,如果没有过期则返回给你,如果已经过期了,那么redis会删除这个key,不会返回给你。
8. redis的缓存淘汰机制(内存不足的时候使用)
  1. allkeys-lru:当内存不足以容纳新写入数据时,移除最近最少使用的key,这个是最常用的
  2. LRU【最近最久未使用(指的是最久未用的)】
  3. LFU【最近最少使用(指的是使用次数最少)】
  4. 一般淘汰机制就是根据上面两种算法而来的,具体看上面的文章
9. 如何进行缓存预热
  1. 项目启动的时候调用,不过总可能存在遗漏的。
  2. 热点数据那么多,我们也不可能提前知道哪些是热点数据,就容易造成击穿、穿透、雪崩,这个时候就要按照情况解决这三种问题。
  3. 热点数据可通过我们系统的接口统计工具来判断哪些接口是热点数据
10. redis缓存不一致问题
  1. 先改后删除(下面有详细介绍)
  2. canal 去更新缓存
  3. MQ去更新缓存
11. redis主从不一致问题
  1. redis是弱一致性,异步的同步数据,确实会存在不一致问题。
  2. 忽略这个数据不一致,在数据一致性要求不高的业务下,未必需要时时一致性。
  3. 强制读主库,使用一个高可用的主库,数据库读写都在主库,添加一个缓存,提升数据读取的性能。
  4. 选择性读主库,添加一个缓存,用来记录必须读主库的数据,将哪个库,哪个表,哪个主键,作为缓存的key,设置缓存失效的时间为主从库同步的时间,如果缓存当中有这个数据,直接读取主库,如果缓存当中没有这个主键,就到对应的从库中读取。
12. redis 的持久化原理
  1. RDB:在指定的时间间隔能对你的数据进行快照存储。全量数据的数据备份。
  2. AOF:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。
  3. rdb更好
    在这里插入图片描述
13. redis为什么这么快

https://blog.csdn.net/weixin_43001336/article/details/122773260
https://blog.csdn.net/qq_35190492/article/details/122293594

14. redis高级数据类型
15. redis红锁

https://blog.csdn.net/jiangxiulilinux/article/details/107015292

16. redis主从问题

https://blog.csdn.net/ghw15221836342/article/details/117433888
https://www.cnblogs.com/tsaiccj/p/15787349.html




8. spring cloud相关

会问你用了其中的那些组件,都是怎么使用的,这个我不怎么能回答上来alibaba和网飞两套都可以混合着说,就可以直接说我们项目用的那些,说了 spring gateway 他就会问你里面有些什么功能点,怎么实现的




9. 并发场景下这么保证数据的安全性和系统的可用性

限流、加锁、加队列等等,还是得百度




10. 线程池相关的问题

问题1:(1和10的核心线程和最大线程数)例如100个线程进来,他会进行一些什么步骤

https://www.cnblogs.com/shijianchuzhenzhi/p/12964740.html
https://blog.csdn.net/lchq1995/article/details/85230399



问题2:线程池的几个参数说一下(大概说出来就行了)

https://www.cnblogs.com/shijianchuzhenzhi/p/12964740.html
https://blog.csdn.net/lchq1995/article/details/85230399




11.spring ioc容器和aop相关的问题

这个自己看吧,可以说spring创建流程和三级缓存还有aop是如何生成的。不会就背




12. mysql相关(下面还有个慢查询)

1. mysql 事务隔离级别

ACID等一系列东西,看得深的可以说那些日志(redolog、undolog),mvcc实现原理等等
https://www.cnblogs.com/kismetv/p/10331633.html (ACID等全面的介绍)
https://blog.csdn.net/Waves___/article/details/105295060 (mvcc原理)




2.mysql优化
a.选择表字段类型的优化建议:
  1. 越小越好:存ip可以用函数转换,然后再存整型数字(这样比存字符串解约了很多空间)
  2. 尽量避免使用null:更难优化,索引都不会包含null列,尽量避免设计为null
  3. 数据类型:越小越好,解约空间
  4. 要注意varchar是设置了255,用了多少就占用多少,char是设置了255就占用255的大小(注意:varchar不会像win10一样这样提前占用,但是会分页存储数据)
    在这里插入图片描述
  5. 选择时间按照需求选择,datetime,timestamp,date 这三个时间按照需求选择。
  6. 关于char 和varchar。如果数据行的长度导致原有的存储位置无法存放,那么不同的存储引擎会做不同的处理。例如 MyISAM 可能产生数据行的碎片,而 InnoDB 需要进行磁盘分页来存放更新后的数据行。​
b. 其他优化

1、适当的添加冗余字段
2、大字段单独切分为一张表
3、在索引字段上使用not,<>,!=。不等于操作符是永远不会用到索引的,因此对它的处理只会产生全表扫描。优化方法:key<>0改为 key>0 or key<0。(not,<>,!=是能使用索引的,具体会不会用还得看成本问题)
具体文章:https://www.cnblogs.com/niuben/p/11197945.html
非常好的联合索引介绍:https://zhuanlan.zhihu.com/p/115778804




3. 介绍下mysql 的锁

https://mp.weixin.qq.com/s/fmSHG0SejfD0IdnpIYHT9w
https://zhuanlan.zhihu.com/p/48269420(详细介绍间隙锁)

  1. mysql有共享锁s和排它锁x,使用 select…lock in share mode 会加记录锁,使用 select… for update 会加排它锁。
  2. 共享锁意思是所有人都能访问,但是都不能修改,排它锁是其他都能查询,但是只有持有锁的能修改。
  3. install会加一个隐式的锁,用于保护插入的数据
  4. update和delect会根据查询条件去加响应的锁(这个才是最麻烦的地方)
  5. Recore Lock:记录锁,锁定的是一行的数据,他这个锁具体的是加在索引字段上面
  6. Gap Lock:间隙锁,是插入索引记录中间的锁,锁定的是一个范围,如果查询条件没有命中索引(),则会去加间隙锁,命中了则加记录锁。(rr级别下才有)
  7. Next-Key Lock:临键锁=记录锁+间隙锁,间隙锁锁不包含记录本身,而这个包含
  8. 注意,一个查询不一定会只加一种锁,会根据不同的情况发生变化,例如查询的列是不是索引,是何种索引,都会有不同的加锁情况
  9. 锁就只有:记录锁和间隙锁和 Gap Lock 这三种,都属于排它锁
4. mysql的执行顺序(没人问)
from 
join 
on 
where 
group by(开始使用select中的别名,后面的语句中都可以使用)
 avg,sum.... 
having 
select 
distinct 
order by
limit 

https://blog.csdn.net/u014044812/article/details/51004754   (具体看这个文章)
5. mysql 不使用索引情况

https://mp.weixin.qq.com/s/ns9eRxjXZfUPNSpfgGA7UA (两个字段字符集不同导致不使用索引)

6. select count(*) 为什么这么慢

https://blog.csdn.net/qq_48157004/article/details/127216519




13. jvm相关问题

1. 项目中gc用的什么算法?
  • 解决1:一般是用分代算法,年轻代使用复制搜集算法,老年代使用标记清除或者标记整理算法。在1.7的时候出现了g1收集器,的整体内存区域是一块一块的内存空间

    解决2:具体的落地垃圾回收器是使用了CMS收集器,它主要就是基于标记清除来实现的,可以并发收集,低停顿,缺点就是有内存碎片

    复制搜集算法:把年轻代分区,并且把那些没有gc的对象从edent放到 service区,一层层复制
    标记清除算法:标记要清除的对象,一次性全部处理,会存在内存碎片 标记整理算法:标记所有要清除的对象,把它们移动到指定的内存区域,然后释放掉遮掉内存区域,会费时间,但没有内存碎片
    https://blog.csdn.net/qq_45076180/article/details/107766779

2. gc时机

https://www.cnblogs.com/leeego-123/p/11298342.html

3. 内存模型

在这里插入图片描述

4. String 为什么用final修饰?(可由此引出jvm相关知识)

1.保证了字符串对象在多线程环境下是线程安全的(同一个字符串实例可以被多个线程共享等等)
2.因为String类的不可变性,才能使得JVM可以实现字符串常量池


5. jvm字符串常量池的使用

https://blog.csdn.net/weixin_40304387/article/details/81071816(参考)
https://segmentfault.com/a/1190000009888357

  1. 字符串常量池就是用于存放 string类型的变量
6. 如何解决线上gc频繁的问题

https://blog.csdn.net/jushisi/article/details/111179520

  1. 如果是本地,在启动参数里面添加jvm命令,查询gc日志,再分析gc是fgc还是ygc,并根据这条命令去判断堆栈哪个空间变化最大
  2. 本地可以通过jps命令去查询进程id,我们可根据进程名称找到我们的java 服务,并通过 jvm 的 jstack 命令去下载gc堆栈信息
  3. 也可以通过jdk自带的工具,jc和jv去查询是否有死锁和查询详细的堆栈信息。
  4. 去找到我们的最大对象。堆栈满了肯定是对象导致的(通过 jmap 查询最大对象)

在这里插入图片描述

7. 常用的jvmt调优工具和调优命令有哪些

https://www.cnblogs.com/jpfss/p/8488111.html
1、工具:jconsole.exe、jvisualvm.exe、Java Profiler
2、命令:https://blog.csdn.net/jushisi/article/details/111179520




14. 锁相关问题

1. 你知道有哪些锁
  1. 分布式锁(redis),java自带的单体服务锁(syn关键字、lock工具锁)
2. syn和 lock不同点和相同点
不同:
 - syn是c++实现的,lock是java实现的,
 - lock可手动释放锁,lock可以设置为公平锁,因为其中有许多
 - syn关键字内部有一个锁升级的步骤(偏向锁->轻量级锁(自旋锁)->重量级锁(用户态转为内核态))(不懂这些就不要说了)

相同:
 - 都是可重入锁
 - 都是非公平锁
 - lock和syn如果碰到大量请求都会丢掉相应的队列里面去

https://mp.weixin.qq.com/s/2yxexZUr5MWdMZ02GCSwdA (锁升级)







15. spring cloud相关

1. 介绍下cap理论

https://blog.csdn.net/as4589sd/article/details/115807954

CAP定理:
        指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可同时获得。
cap理论中:
  1. 一致性和可用性,我们在访问服务的时候,B服务有两个副本,这个时候,我们重启所有B服务,这个时候b服务的副本还会短暂停留,我们并不能获取到最新的b服务,个人认为这个就是没展现一致性的地方,但是这个时候我们可以访问到b服务没重启之前的服务代码,这就是可用性的体现。
  2. 假如每次重启b的时候,旧服务都直接不能访问,那么这个时候就是一致性了,但是你不能访问到b服务,那么你的可用性就没了。
  3. 一般微服务都是多个模块,我们通过A访问B,B节点没了,我们A还是可以访问的,这就是分区容错性,保证了B服务出了问题,不影响到A服务。
实际使用:
  1. naocs可在cp和ap之前自己做选择
  2. Zookeeper保证的是CP,放弃可用性
  3. Eureka保证的是AP,放弃一致性(他的集群最多支持3个)
2. spring gateway 的作用和其他网关的比较
3. nacos 和Eureka 的区别

16. mq

mq如何保证消息不丢失
1、手动确认消息,消费端消费完成通知服务端消息已经被消费了
2、消息持久化,如果mq服务死掉了,那么消息就会消失
3、消费丢失的大部分原因是因为mq队列满了,服务器出问题了,这个时候我们可以通过死信队列来处理那些丢失的消息,可设置一个消息超时时间,超时没被消费则放到死信队列里面去处理。
mq如何保证消息不被重复消费
原因:消息队列卡死导致没有收到消费者消费成功的信息,那么消息队列重启成功之后则会再次发送消息给消费者
1、简单点的,代码幂等性,即多次消费也不影响接口的数据
2、复杂点的,设置一个全局的事务id,存放在redis里面,消费之前去查询发送过来的事务id存不存在,不存在则表示已经被消费掉了
mq如何处理消息积压问题
1、首先需要扩容服务器,保证消息还能继续去被消费
2、通过监控和日志排查,到底是哪个地方出现问题,并立马进行优化




二、项目问题

1. redis缓存不一致问题

  1. 事情: https://blog.csdn.net/qq_35433716/article/details/86375506
    在使用redis做缓存的时候,去修改数据库的数据,并在修改的时候更新redis中的数据,但是在修改完之后,缓存中还是原来的数据。

  2. 原因
    a. 修改的时候,b线程去查询数据,但是发现缓存中没数据了,可能是失效了,就去数据库查询了这个数据,发现缓存中没有,他就到db中查询,然后放到缓存中去的是旧数据
    b. 线程a修改了数据,同时线程b修改了数据库,又修改了缓存,这时候线程a修改缓存,就造成了数据一致性问题

  3. 我们的设计是什么样的
    修改之后,再去更新缓存

  4. 改成什么样解决了问题
    修改之后,删除缓存信息

  5. 其他问题:

     	1. 缓存刚好失效
     	2. 线程A查询数据库,得一个旧值 
     	3. 线程B将新值写入数据库
     	4. 线程B删除缓存
     	5. 线程A将查到的旧值写入缓存
     	要达成上述情况,还是说一句概率特别低;要解决可以使用异步删除,或者选择不使用缓存
    
    解决: 
    	各种解决都不是很完美的方法,具体看场景
    	https://blog.csdn.net/u010180738/article/details/105786711(看这篇文章)
	额外知识点:
		redis 穿透击穿:https://www.cnblogs.com/windpoplar/p/10923830.html
		mysql主从同步不一致问题:https://mp.weixin.qq.com/s/roe8Qa1QIEc0DPTSoGoqyw
		同步工具:canal(通过binlog日志来同步数据)
		https://github.com/alibaba/canal/wiki/ClientExample
		双删文章(有写主从怎么删除):https://blog.csdn.net/zhongxiangbo/article/details/85494154



2. mysql死锁问题

场景:有一个需求是需要删除数据之后,去新增一条数据,并且是在同一个事务里面,这个时候可能就会发生死锁。具体看代码和其中的文章

    @ApiOperation(value = "批量修改数据3(必定死锁场景)")
    @GetMapping(value = "/updateData3")
    @Transactional
    public Result<?> updateData3() {
        userInfoService.list(new LambdaQueryWrapper<UserInfo>().last("limit 2")).parallelStream().forEach(e-> {
            /*
                参考文章: https://my.oschina.net/u/2342969/blog/1813772/?p=1
                1. 删除不存在的数据,那么他就会使用间隙锁,锁定[100,1000)(假如有id=100的数据)(上面一定要开事务,不开就没用)
                2. 间隙锁之间并不产生冲突,但间隙锁与插入数据之间会产生冲突(所以两个线程删除之后不会报错)
             */
            userInfoService.removeById(1000L);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException interruptedException) {
                interruptedException.printStackTrace();
            }
            //2. 保存一条数据,新增的时候id数据库自增,会在锁的范围内
            userInfoService.save(e);

        });

        return Result.data(1);
    }



3. rabmq消息问题(问到mq就会问消息丢失、消息重复消费这两个问题)

  1. 例如消息丢失问题:具体的场景是不同环境配置了同一个mq,导致消息不知道在哪里被消费了,有时候能正常消费,有时候又消息丢失了。其他消息丢失的场景可以说使用mq的消息确认机制,来确认是否接受到这个消息(我之前碰到的就是多个环境都连接了同一个mq,导致消息时不时看不到消费信息,其实是被另外一个环境的服务消费掉了)

  2. 消息重复消费:如果是一些幂等性的操作的话,重复消费也不会有问题;另外的是通过存一个第三方id来保证消息是否被消费过(具体自己查询相关文章吧)
    https://blog.csdn.net/qq_41864967/article/details/90442085

  3. 哪些场景用到了:可以说例如订单创建成功,异步发送微信通知给用户;还有订单5分钟自动退款(rabmq的延期队列,是通过自带的插件生成的)



5. mysql慢查询的问题

  1. 这个具体自己编了,可以说订单表数据量很大,几千万上亿条,然后表设计又不合理,只能通过三表联查才能查询到数据,这个时候能做的就是说加查询条件才能查询数据,其他时候不让用户去查询数据。主要就是说这个上线了,各种优化都没办法进行下去等等。还有做了分表操作,运维按照数据大小,使用时间来区分表数据,然后他就会问你怎么分表,这个我不知道。
  2. 如果你说mysql相关的问题,他就会问mysql 的一些优化点。这个可以先从mysql 表设计讲,说尽量合理,分附属表等等,然后就是从索引方面来讲怎么加好,最后就说查询语句怎么才能用到索引等等。三个方面





三、笔试题

1. String StringBuild SttringBuff 三个区别

这个比较简单




2. 接口和抽象类的区别

笔试题的话,写的话写出重点就行了,不需要写很多
https://zhuanlan.zhihu.com/p/94770324




3. 类的加载顺序(这段屡清楚了就没啥问题了)

public class Test9 {

    public static void main(String[] args) {
       new B();
    }

}


class A {

    static {
        System.out.println("静态代码块A");
    }

    {
        System.out.println("代码块A");
    }

    public A() {
        System.out.println("构造方法A");
    }

}


class B extends A {

    static {
        System.out.println("静态代码块B");
    }

    {
        System.out.println("代码块B");
    }

    public B() {
        System.out.println("构造方法B");
    }
}
输出:
静态代码块A
静态代码块B
代码块A
构造方法A
代码块B
构造方法B




5. 对象的引用和修改(这段屡清楚了就没啥问题了)

public class test2 {


    public static void main(String[] args) {
        test2 test2 = new test2();
        Integer[] integers = new Integer[1];
        integers[0] = 1;
        String str = "a";
        test2.aaa(str, integers);
        System.out.println(str);
        System.out.println(integers[0]);
    }


    public void aaa(String str, Integer[] integers) {
        str = "b";
        integers[0] = 2;
        System.out.println(str);
        System.out.println(integers[0]);

    }
}

输出:
b
2
a
2

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值