🔥来瞧瞧阿里一面都面些什么(二)| 掘金技术征文

2,327 阅读17分钟

前言

文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…
种一棵树最好的时间是十年前,其次是现在
我知道很多人不玩qq了,但是怀旧一下,欢迎加入六脉神剑Java菜鸟学习群,群聊号码:549684836 鼓励大家在技术的路上写博客

絮叨

话说我上篇文章不是已经挂了吗,怎么又来水文章了,难道我是看上了掘金的奖励了哈哈。。对我就是看上了,哈哈,下面是上一篇博客的地址

故事起因

一大早,群里一个老哥发私信给我说,它和我有一模一样的面试经历,有些东西想请教我,然后就聊上了,这个老哥是在我们老家衡阳读的大学,顿时有点情切感,然后和它聊了很久,发现了一个很大的问题,同时渣渣二本,怎么差距就那么大,它的实力还是可以的。他已经过了3面了 我估计还有2面就能过了,同时也说明,我们这些渣渣本科,只要好好学习,也还是有机会的,然后我就借此把它的面试题要过来了,然后我自己尝试的去回答一下, 算是一个复习吧。

感谢写文章,能让我们在成长的路上共同进步,所以有想一起学习的,快来加群吧,虽然没有啥大佬,但是我觉得一起学习,比有大佬的群 更重要,这就是一个氛围吧。QQ群聊号码:549684836

老哥的一面面试题

  1. 线程的几种状态?哪些状态有锁。
  2. 说说线程池?
  3. 线程池的拒绝策略?
  4. 让你自己设计一个线程池你如何设计?
  5. lock和synchronized区别?
  6. jdk对synchronized做了哪些优化,讲讲锁升级过程?
  7. gc熟悉吗?讲一下CMS?
  8. CMS跟G1的区别?
  9. G1的设计有什么特点(优势)?
  10. 常见的类加载器有哪些?
  11. 什么是双亲委派模型?
  12. 如何打破双亲委派模型?
  13. 你平常用的API中有哪些是打破了双亲委派的?
  14. 类加载的过程详细说说?
  15. 讲一下项目的基本架构?用到了哪些中间件?
  16. 讲一下Redis击穿,穿透,雪崩?
  17. Redis主从和哨兵说说看?
  18. 讲一下常用rabbit,kafka,activemq,rocketmq的区别?
  19. kafka如何保证消息不丢失?不重复消费?
  20. 那kafka如何保证顺序消费?
  21. 分布式锁有哪些实现方式?
  22. Redis分布式锁是怎么实现的?
  23. 项目中碰到过死锁吗?产生死锁的原因是什么?
  24. 高并发情况如何对项目做优化?
  25. 项目中碰到过什么难题?
  26. 你有什么想问我的吗?

我的回答

Tips 这个就是我自己还原一下场景,然后我就当我自己是在被面试,看看自己会怎么去回答这些问题,看看自己的不足,其实读者也可以去尝试一下,算是一个查漏补缺吧

线程的几种状态?哪些状态有锁。

在Java线程中有以下几种状态,初始,运行中(就绪和运行) 阻塞(调用同步方法),等待,超时等待,和死亡这几种状态,在阻塞方法里面是有锁的,然后线程中的wait方法是会释放锁的,所以join方法也会释放锁,底层是wait,而sleep,yield是不会释放锁的

说说线程池?

线程池就是一个池技术,类似于我们的数据库连接词,可以减少线程创建的时间开销。当有任务来的时候,可以直接对任务进行处理,提高响应速度。然后我再把线程池的运行过程补一下
首先一个任务调用execute方法的时候的大致流程

  • 如果当前运行的线程少于corePoolSize,则创建一个corePoolSize去执行任务(创建的过程是加锁的)
  • 如果当前线程大于corePoolSize,那么就把当前任务加入到一个阻塞队列中去。
  • 如果说当前阻塞队列也满了,则会创建一个新的线程来处理任务
  • 如果判断创建新的线程之后,线程池的数量>maxPoolSize,则把他交给拒绝策略去处理。

线程池的拒绝策略

当线程池的处理任务的能力达到饱和之后,我们可以选择以下的几种策略

  • 默认的就是直接抛出异常
  • 直接抛弃该任务
  • 抛弃阻塞队列中的第一个任务,把这个任务加入到队列里面
  • 直接由提交任务的线程去执行任务,然后阻塞当前提交任务的线程。

让你自己设计一个线程池你如何设计

首先让我设计一个线程池,我会考虑我线上的硬件水平如果我是4核的话,那么我线程池的核心线程 我的核心线程最多给4个 然后最大线程数给8个,如果是8核就 核心线程数给6个 最大线程数给12个。其实这个还是得看场景是cpu密集型 还是io密集。但是上面的方案其实是一个折中的方案
然后设置一下线程的存活时间给个3分钟吧,他的拒绝策略,就用最后一种吧就是当满了的时候,用提交的线程去执行任务。设置他的一个阻塞队列LinkedBlockingQueue 基于链表的队列 吞吐量稍微大点 Spring的线程池的实现默认就是他,然后设置一下任务的个数,设置个500差不多了。看实际业务,一般项目里面的线程池我们就直接用Spring的实现就行了。

lock和synchronized区别

相同点,都是为了线程安全,

不同点

  • synchronized 是一个Java关键字,二lock是一个Java类,一个是JVM层面,一个代码层面
  • synchronized会自动释放锁,但是lock要手动释放 在抛异常的时候。
  • synchronized是非公平的,lock可以是公平的也可以是非公平的
  • synchronized是悲观锁(锁升级之后),而lock底层是cas 乐观锁的实现

jdk对synchronized做了哪些优化,讲讲锁升级过程?

就是锁升级的过程
当一个线程访问一个含有synchronized的方法 代码块的时候,首先会去锁住的这个对象的对象头里面的markword的锁标记位从无锁变成一个偏向锁的过程,并且记录当前的线程id,第二种情况当一个线程来访问这个同步方法的时候,当前锁对象的锁标记位已经是偏向锁,然后就对比线程的id,发现线程id并不匹配,那么就会进行一个偏向锁撤销的过程,这个过程会暂停偏向锁的线程(正好当前线程在执行的情况),如果当前线程还需要获取锁,则升级为轻量级的锁,通过cas来获取锁,并改变markword的标记位,如果一个线程的cas次数超过10次,则会升级成重量级锁。进入一个阻塞队列,是一个非公平的锁。

gc熟悉吗?讲一下CMS?

还行,CMS是一种垃圾回收器一般配合新生代的ParNew来使用。它的目标是以最小的停顿时间为目标的一个垃圾回收器,基于标记清除的算法实现
它的GC分为4个阶段,再GC的日志中也是有体现的

  • 初始标记阶段,这个阶段是用来标记GCRoot的,此过程会触发Stop The World
  • 并发标记,根据GCroot 来标记需要清除的对象
  • 重新标记,这个过程也要Stop The World 因为再并发标记的过程中,可能有新的垃圾对象进来了,所以需要再次检验一下。
  • 并发清除,把前面标记的垃圾对象,清除掉。

CMS跟G1的区别

其实G1的回收机制和CMS很像,但是他们的区别就是region的概念,然后就是把内存分成了2048个分区,然后可以对部分的区进行回收,这样回收的对象就会小很多,那么每次Stop The World的时间就会少很多,所以对于大配置的机器用G1比较好,为啥呢?如果我们cms 那么等我们Full GC的时候,我们停顿的时间会很长,对于系统来说是有很大的影响的。

G1的设计有什么特点

和上面差不多,可以由我们手动控制 Stop The World的时间,这点是非常牛逼了。G1收集器基本上不牺牲吞吐量的情况下完成低停顿的内存回收;G1将Java堆(新生代和老年代)划分成多个大大小小的独立区域, 然后进行区域回收

常见的类加载器有哪些

  • 启动类加载器(最顶层)
  • 扩展类加载器
  • 应用程序加载器
  • 自定义加载器

什么是双亲委派模型

总的来说 八个字,向上检查,向下加载
如果一个类接受到类加载请求,他自己不会去加载这个请求,而是将这个类加载请求委派给父类加载器,这样一层一层传送,直到到达启动类加载器(Bootstrap ClassLoader)。 只有当父类加载器无法加载这个请求时,子加载器才会尝试自己去加载。

如何打破双亲委派模型

使用线程上下文类加载器,可以在执行线程中,抛弃双亲委派加载链模式,使用线程上下文里的类加载器加载类.

你平常用的API中有哪些是打破了双亲委派的

tomcat 因为它一个tomcat里面可以放多个wabApp,所以它需要打破

类加载的过程详细说说

类的加载过程

  • 加载就是把.Java文件 加载到JVM里面变成.class文件
  • 验证 验证文件是否合法
  • 准备 给对象的基本数据类型赋值默认值,对引用类型分配内存空间
  • 解析 将符合引用替换成真实的地址引用
  • 初始化,直接初始化一个对象
  • 使用 就是用的过程
  • 卸载

讲一下项目的基本架构?用到了哪些中间件?

我们项目是一个类似于2B2C的一个教育平台对标网易云课堂和腾讯课堂,然后才有的是微服务架构,分布式系统开发,把整个系统拆分成了大概10多个基础服务 例如商品 订单 资讯 用户 sso 支付 直播 录播 等等,和几个公共服务注册中心 分布式配置中心 分布式系统调度中心 等等,然后中间件 用的rabbitmq redis cannal,es,skywalking 等等。

讲一下Redis击穿,穿透,雪崩?

  • 雪崩就是当大量的缓存失效,导致了大量的请求打到了我们的db导致db崩溃的现象 这种我们一般是加一些随机的过期时间,避免大量的key同时失效。
  • 击穿 就是一个hot key 或者几个 hot key 直接把redis打崩了,就是正面刚,干掉了你,一般这种情况很少,因为我们肯定会预估我们的系统流量的瓶颈,在流量进入redis之前最限流操作的,或者我们会给redis集群,提高redis的吞吐量
  • 穿透,这个是有可能的,就是当我们请求api的时候,有时候有一些非法的key 进入到了我们的逻辑层,然后跳过了redis,然后直接把流量直接打到了我们的db,这种解决方式也很简单,可以加一个过滤器,当我们存这个数据到redis中的时候,把他的key 也存到bitmap中,这样查的时候先查bitmap如果里面有就让他去redis中拿,不然就直接返回,把流量拦截。

Redis主从和哨兵说说看

主从的话就是主节点负责写,其他节点负责读,这样来提高redis的吞吐,但是这样也有单点故障问题,所以我们线上的环境最好用哨兵模式,哨兵可以监控redis 集群节点,可以做崩溃恢复,当主节点挂了,他可以选举新的主节点,来保证单点问题。

讲一下常用rabbit,kafka,activemq,rocketmq的区别?

首先 activemq 不会在选择了 社区的活跃,性能都不在考虑的范围,对于技术选型的话,首先就干掉它

  • 吞吐来说 kafka 和rocketmq 的吞吐要比rabbit 高很多
  • 时延 rabbit 最低的延时,但是后面都差不多
  • 社区 rabbit活跃度不错,rocketmq 阿里的 也不错,kafka 也还行
  • 可用性 rabbit 基于主从,其他2个基于分布式系统,可用性比他高很多
  • 其实除去这些对于技术的选型,要考虑团队的熟悉度,还有你的场景,rocketmq 他的事务机制,对于很多场景是不错,kafka对于日志等场景是非常不错的,rabbit对于小公司也是很不错的,所以这个选型没有说哪个最好,只有最合适。

kafka如何保证消息不丢失?不重复消费?

其实这种问题对于任何一个mq的回答都是通用的,我们公司用的rabbitmq 我就用rabbitmq来回答了
肯定是从三个地方来回答这种问题

  • 发送端 保证发送端的消息一定是能够发送到我们的中间件里面,可以采取一个cobnfirm机制,如果失败了,就重新发送,
  • 在我们rabbitmq的时候,消息也可能丢失,这个时候,我们得做持久化。
  • 消费端,我们要有ack机制,就是说必须说确定消费成功后,才能ack
    对于重复消费,这种问题,我们就可以做幂等校验,发送之前我们可以存一个唯一key,消费完之后删除这个key,如果发现没有这个key说明之前已经消费过了,这样我们就可以做到防止重复消费。

那kafka如何保证顺序消费

kafka的顺序消息仅仅是通过partitionKey,将某类消息写入同一个partition,一个partition只能对应一个消费线程,以保证数据有序。如果是rabbitmq其实也是一样的,只是说我们的性能就没有那么好了。同一个队列,对应一个线程去消费。

分布式锁有哪些实现方式,Redis分布式锁是怎么实现的

对于分布式锁的实现方式 业届一般有以下的实现方式

  • 第一个在数据库层面采用表锁来实现,在数据库中存方法名,并给他加上唯一索引,这种方式目前用的少,性能不怎么行
  • 第二个用zookeeper 临时顺序节点和他的watch机制来实现分布式锁,但是这种方案因为我们公司没有zk,所以没有采用,用的是下面这种方案
  • 通过redis 来实现分布式锁,我们知道一个分布式锁的四个条件就是 加锁,解锁,锁超时,为锁续命,那么我们的redis的setNX EX我们用lua把这个命令做成一个原子操作就可以实现一个分布式锁。但是考虑到业务的复杂性,我们公司用的是Redisson 来实现分布式锁,这边我说说Redisson实现分布式锁的原理,它其实很简单就是getLock 获取锁对象,然后调用tryLock方法,这个是一个重入的一个分布式锁的实现,参数可以是过期时间,和尝试最大获取锁的时间,当我们调用这个方法的时候,首先是记录进入的时候,然后判断一下最大尝试获取锁的时间,是不是达到最大,如果没有达到最大,这个时候就去尝试加锁,这个时候如果已经有人加锁了 就会返回过期时间,如果返回的时间为0 这个就说明,此时这个时候是没人加锁的,这个时候,当前线程就可以获取锁,然后再次判断一个最大尝试获取锁的时间,超过了就直接返回获取锁失败,接下来就是当前线程要去订阅刚刚获取锁的那个线程的释放锁的事件,方便我这个线程下次去竞争锁,然后进入到一个循环等待的过程,每次收到通知的时候,会判断一下自己是不是超过最大时间了,如果超过了就直接返回锁失败,如果获取锁返回为0时,去尝试加锁,当加锁成功会返回null,不然就返回获得锁那个线程的过期时间。

项目中碰到过死锁吗?产生死锁的原因是什么?

这个我还没真没有碰到过

高并发情况如何对项目做优化

这个要回答的话,估计很长了,我说下自己的一些小思路吧,这个应该设计的系统架构的设计了,而且是高并发系统架构

  • 首先前端 我们前端的 html 和css js 通过cdn 加载,这样可以提供用户加载的速度
  • 然后是性能问题,正确估算性能的瓶颈,比如说做缓存前置,限流,尽可能的保证我们的应用程序一直可以用。
  • 可以做一些流量的削锋,异步的消费这些流量。
  • 数据库层面 读写分离 分库分表,等等。或者用TiDB
  • 然后服务拆分,尽量保证,一部分服务不影响你的整个项目

项目中碰到过什么难题

系统重构,业务的代码的优化,然后报表拆分,sql里面全是业务,然后重构成代码层面。迁移和聚合TB级的数据,然后保证数据的准确性。最后通过团队的配合,然后努力 最后无缝迁移到新的架构,然后把BI功能成功从原来的耦合的业务性能拆分出来。保证我们的业务系统的性能提高很多,并且后面的迭代能够更加轻松,对于系统的健壮性也提高不少。

你有什么想问我的吗

贵公司的技术栈,目前的项目类型。等等

结尾

上面是我自己一边看题目一边想的回答,估计真实面试场景的话,可能最好结合自己的项目来说吧,量化自己项目的指标可能会好点,比如多少的qps,然后TB级别的数据你是怎么处理的,然后怎么的,哈哈。我的回答大家看看就好,肯定也是很菜的,不过我写这个就是想把这个当做一次面试经历来写的。上次挂在了算法上,最近在B站学数据结构和算法,虽然不是科班出身,但是学习是没有边界的。最后希望大家一起努力吧。感谢这个小伙伴的面试题分享。努力的路上你并不孤单,加油!

下面是我的公众号,目前是啥也没有?以后会通过公众号给大家一些学习资源啥的,然后就是写文章。尝试着去运营一下。 回复 888 海量的学习资料

日常求赞

好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是真粉

创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见

六脉神剑 | 文 【原创】如果本篇博客有任何错误,请批评指教,不胜感激 !