笔记|咖啡汪对敖丙老哥Java后端面试心得体会————阿里一面

自我介绍:
XXX
之前做过的项目有两个:
1.政企业务的支撑系统
偏业务,主要做了两个
(1)业务沟通,系统迭代
(2)搭建线下的预发环境,测试环境,去用做CI,CD(https://blog.csdn.net/qq_18975791/article/details/85860105),用CI,CD做一个敏捷开发
(3)对服务进行了一个模块化的划分,把原来一个集成度很高的项目,拆成了一个主Web服务,拆出来了附件服务,定时任务,还有Redis
帮助去更好地做一个服务能力的沉淀和模块化
(4)用设计模式对现有代码进行重构,使得一套代码可以跑起20多个流程,同时因为是线上系统,对线上Mysql要求比较高,对Mysql也做了一个性能上的大量调优的一个探索的过程
基础的,常见数据结构的一些了解,HashMap的数据结构:
HashMap在1.7和1.8,做了一个比较大的改变,1.7之前使用的就是
数组加链表,然后它的数据节点是一个Entry节点,是它的一个内部类
HashMap1.7之前就是,它的过程,它的数据插入的过程是使用头插法,但是HashMap使用头插法会造成什么问题呢?它在resize,也就是它在扩容的过程中,里面有一个resize方法,它又调用了一个transfer的方法,把里面的一些Entry进行了一个rehash,然后在这个过程中可能会造成一个链表的循环,就可能在下一次Get的时候出现一个死循环的情况,也有可能就是因为他没有加锁,所以多线程并发情况下可能对他的数据,不能保证他数据是一个安全的,就是我push进去的值,取出来的还是我push进去的一个值
——————需要处理:hashMap死锁分析:https://blog.csdn.net/lantian0802/article/details/42487803 HashMap 多线程push造成死锁:https://blog.csdn.net/futurewrg/article/details/73469639
jdk1.8以后把他变成了一个链表加数组加红黑树的结构,把原理的一个Entry节点也变成了一个Node节点,它的整个put的过程也做了一个优化
他的扩容机制:
他这个capacity(容量,性能),这个节点就是我在初始化这个HashMap,如果我们没有设置capacity,他的默认初始化容量是16,然后负载因子是0.75,他会自动计算出他的一个threshold,他的一个阈值,
16*0.75=12,就是一个扩容的阈值,如果当我push的时候,会先判断当前的size是不是会大于当前的阈(yu`)值,如果大于的时候,他就会扩容成原来的2倍,将原来的一个Entry进行一个resize的这么一个过程
1.7之前是头插法,1.8之后是尾插法,头插法有死循环,是他线程不安全的原因之一,那1.8之后他线程就是安全的吗?
他也不是线程安全的,因为他使用了尾插法,没有改变他原来数据插入的这么一个顺序,所以他在这儿不会出现一个链表的循环。
HashMap线程不安全,那么日常如何保证他的一个线程安全的开发呢?
会用ConcurrentHashMap这种线程安全的一个集合容器
线程安全的,也可以用hashtable,给他加synchronized,lock,或者用Collection.Synchronized都可以对他进行一个同步的操作,为什么你选择了CurrentHashMap?
第一个是,ConcurrentHashMap他的并发度是更高的,因为就是在我看来ConCurrentHashMap,我们知道就是普通的HashTable是直接对里面的方法进行了一个Synchornized,就是加了一个对象锁。
但是ConcurrentHashMap数据结构就是1.8以后变成了同样的数组加链表加红黑树,然后他只会锁住我目前获取的那个Entry所在的那个节点的那个值,然后在上锁的时候使用了CAS ()+Synchronized
需要处理:什么是CAS?看一下汪哥
然后再加上jdk1.6以后对Synchronized所做的一个优化,就是锁升级的一个过程,所以他的效率是更高的,就是他支持的一个并发度是更高的。
简单介绍一下锁升级的过程?
就是在最开始我们这个锁是支持偏向锁的,我当前获取到锁资源的这个线程,我会优先让他再去获取这个锁;如果他没有获取到这个锁,就升级成一个轻量级的,一个CAS的锁,就是一个乐观锁,然后乐观锁的时候他是一个比较有交换的过程,如果CAS没有设置采购的话,他会进行一个自旋,然后自旋到一定的次数之后,才会升级到Synchronized的就是这么一个重量级的锁,这样就能保证了他的一个性能的问题。
需要处理:什么是偏向锁,乐观锁,自旋锁
其实你还是漏了一步,他应该最开始是无锁的状态,就是一上来他会先去判断一下,然后在升级,不过没关系,核心你都说上来了
Spring的事务隔离级别大概有哪几种,你知道吗?
事务隔离级别7种,比如required,required就是我支持当前事务,如果没有当前事务,还有Supports,如果没有当前事务就以非事务的运行,然后还有Mandatory,如果没有事务我就抛出异常,然后还有Not_Supported就是非事务运行,如果有就挂起,还有Never就是以非事务运行,如果有就抛出异常,还有就是一个Requires_New就是支持一个内嵌的事务过程
你现在开发过程中,使用的事务隔离级别最常用的是哪一个?
Required
这和数据库的事务隔离级别有什么区别?
需要处理:Spring的事务隔离级别
AOP熟悉吗?他的主要的实现方式有哪些?或者说你的项目中就是使用到的AOP你也可以简单地给我介绍下
熟悉的,我们在项目中使用的AOP,有,可以去做全线预热,还有一些日志的操作,因为他是将,他可以订一些切点,将那个切点的一些,切面动态地植入进去,呃,就是权限的一些判断,比如我们用JWT进我们的系统之前,可以先AOP拦截下来然后判断他的这个权限,然后也可以去把我们的一些日志操作,相应的给写,也就是做一些变更
然后AOP主要就是有两种框架,一个就是使用JDK,因为他是使用动态代理去做的吗,主要就是可以第一个就是JDK的proxy,第二个就是cglib
他们有一点区别,就是 JDK proxy他主要是可以生成与原类接口,就是与原类实现相同接口的,这么一个类;但是如果原来类没有实现接口的话,就是他可能不太合适
如果使用Cglib的话,他就会使用一种字解码的编辑器,就是ASM的一个编辑器,然后他就可以生成
需要处理:什么是cgLib,项目里怎么去用他 https://blog.csdn.net/qq_18975791/article/details/85860105,什么是ASM:https://www.jianshu.com/p/a1e6b3abd789 我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。
java里的反射机制
对原码重构,使之达到一套代码跑多个流程,那么如何去适配各个流程呢,首先就是要用到一些泛型,然后要在运行时获取到我泛型的这么一个运行时的这么一个类型,然后去拿到他对应的一个业务的一个实体类,去放置他的一些业务信息。因为反射会获取到运行时的一些状态,所以就可以达到代码复用的效果
就是动态获取,那你有没有考虑过反射的性能,因为据我了解,反射在大多数情况下,性能并没有那么好
对,因为反射的时候,就是先去方法区里面,就是先去看他这个类有没有加载过,如果没有的话,会有一个类加载的过程,可能的话,就会在一定程度上影响到这个性能。
因为反射是一种解释操作,就是我们需要告诉jvm如何去做,肯定就会比我们直接写代码,直接操作会慢一些
nginx你提到也是挺熟悉的吧,你知道nginx是怎么去做负载均衡的吗?常见的负载均衡的算法有哪些你知道吗?
1.一致性哈希,就跟HashMap做那个(key)一样,去做一个一致性哈希,然后把负载比较均衡地放在各个机器上
2.还有一种是加权哈希,因为每台机器的性能不一样,性能好的,我们可以把他的权重加的大一点,让更多的流量打在某台机器上面
3.轮询,轮询的话效果可能就没有那么好
一致性哈希是什么意思
所有的流量过来是一样的,就是如果我这一次打到了一台机器上,下一次还可以打到同一台机器上
你这边对网络的HTTP,tcp,udp都比较熟悉,为什么是三次握手,不是两次,不是四次?
首先他三次握手是为什么呢?我客户端要给那个服务器报告我要建立联系,顺便把我自己的一个发送能力,发送给服务器,让服务器知道,然后服务器判断我是否可以给你创建连接,然后顺便把服务器自己的一个接收能力返回给客户端,三次握手使得双方的发送能力和接收能力都达到了一个就是我认为就是协商好的这么一个过程。但是协议没有100%可靠的,三次已经够了,那么四次也保证不了100%可靠
其实就是tcp他本身就是一个稳定的可靠的连接嘛,所以
需要处理:什么是tcp,udp,http https://www.cnblogs.com/hanfanfan/p/9579771.html
怎么沟通和协调组员之间的一些任务,矛盾,或者工作压力比较大的情况,你怎么去处理?
作为一个组长,因为互联网这个行业压力本身就是比较大的,我只能按照他们当前的一个状态,还有他们目前的一个能力范围,去给他们安排一个合适的一个需求的一个迭代的过程,然后尽量地把我们的开发规范,还有我们的一个上线,不管说是上线还是我们需求的一个评审的一个过程给做做规范,然后把文档给留规范。这样的话我们的项目组才能更平稳地往前发展。
Mysql调优,数据库使用多个索引匹配的时候有什么经验吗?
索引这一块儿的话,其实是MySQL调优比较大的一个过程。、
我看来的话,就是我们首先可以,就是把它看成我创建一个索引,我在创建索引的时候可能会考虑一下几个因素,首先是覆盖索引,因为覆盖索引可以减少回表的次数,然后MySQL5.6以后对覆盖索引做了进一步的优化,就是支持索引下推的一个功能,我把我覆盖索引所覆盖的一个字段进一步的进行筛选,然后尽量减少回表的次数
这个我们可以在Explain,看他的执行计划的时候,内阁Extra,字段里面与Using index condition,然后我们可以看到的
其实我们可以对他进行进一步的优化,如果我们的存储介质使用的是机械硬盘的话,因为我们知道机械硬盘他是很怕随机读写的,他有一个磁盘寻址的开销,然后我们可以把Mrr开开,就是multi range read,他可以把在回表之前,把我们的ID读到一个buffer里面,进行一个排序,把原来的一个随机操作变成一个顺序操作,这就是覆盖索引可以做的一个优化,因为覆盖也可以避免,比如说排序用到的一些临时文件,可以利用最左原则和覆盖,所以配合,可以减少一些为索引的维护,这一块是可以做的;
还有一块儿就是如果就是对一些普通索引,如果我们的就是一个就是写多读少的服务,并且就是这个服务的唯一性要求,没有那么高,或者我们的业务代码可以保证唯一性的时候,我们可以用普通索引,因为普通索引是可以用Change Buffer的Change buffer 可以把一些写操作给缓存下来,在我们进行读的时候,进行一个merge()操作,这样的话就可以提高我们写入的速度,还有我们内存的一个命中率,这个就是我认为在创建索引的时候可以考虑的一些点,
什么是最左原则,什么是唯一性,Change Buffer怎么使用,什么是内存的命中率
【笔记|咖啡汪对敖丙老哥Java后端面试心得体会————阿里一面】还有一些点就是如果我这个索引走不上,我们应该考虑哪些方面?
1.是不是我们SQL写的有问题,比如说我们对索引的字段进行了一些函数操作,连接的时候,两个表的一个编码不一样,要不然就是我们可以进一步地排查就是那两个字段的类型是不是不一样,比如说:String,交给他的一个Id,如果String跟ID比较的话,我们会把String转成ID,在MySQL里面,就是用到了一个隐式的,一个cast的函数转换
2.是不是索引统计信息有问题,我们可以去Analyize table 重新统计所有信息,因为我们知道这个索引信息并不是一个准确值,他是一个随机采样的过程,可能会出现问题
因为是业务表,可能增删太多,然后业务的一个空洞也多,都有可能造成我们索引选择的一个问题
刚刚你多次提到了Explain分析出来的索引,它一定是最优的吗?
不一定,它可能会选错,因为我们在索引的时候,可能会涉及到一些回表操作,还有一些排序操作,然后可能会有错
有没有遇到过索引建的不好,导致索引走的很差,查询速度慢的这种情况?
我觉得碰到这种情况的话,就首先考虑,就是用那个force index,强制走一个索引,但是这个是不太好的,就是作为一个业务的应急预案,因为他可能就是迁到一个别的数据库里面,他就不支持了,他还需要我们做一个代码的重新发布,这个是不太好的
还有一种就是,我们可以考虑用覆盖索引加最左原则,就是这种原则考虑能不能把这个选错的,给删除掉,这样的话其实还是也是一个优化的方案之一,我觉得而且其实挺常用的
需要处理:什么是 force index
java8的新特性
闭包,Stream流,可读性不一定好,代码会有一些简化。
记录就是那种热点数据,我们要去大批量地更新,
我们可以去把他写在一个内存的临时表里面,因为我们知道innodb,他是会维护一个buffer pool的,如果我们直接把大量的一个数据直接读进去,可能会造成flush的一个操作,就是把脏页刷回MySQL,就是这么一个操作会造成我们线上的一个业务的阻塞,
需要处理:innodb,buffer pool
比如你刚刚说用缓存的方式去操作,说热点数据如果太猛的话,如果你使用Redis客户端,你Redis客户端也访问不到,因为带宽不是打满了吗?这个时候又怎么办,一瞬间redis带宽被打满
有一种本地缓存可以解决下
RPC框架,原理
RPC框架原理,就是我一个服务端,我需要把我的接口暴露出去,我需要在我这个服务端启动的时候,把这个接口发布到我的一个注册中心里面,然后我的客户端就是那个consumer,在启动的时候,就是需要去注册中心里面,找到发布的那个对应的接口,注入到consumer就是那个服务里面,这样的话,就是创建相当于是创建了一个start的一个代理对象,他其实就是把服务之间的一个通信变成了就是封装成了一个对象的调用,但是底下也涉及到了比如说他网络通信和他一个协议的转换
刚刚提到了Http的调用嘛,其实去调用的话就会存在一种超时的问题,你平时是怎么处理这种问题的
补货异常然后重试,单纯的重试,超过一定次数后就会让他放弃。
消息中间件用过吗?日志服务,外部流量正常,但你们日志激增,把线程池打满了,你会怎么去排除这个问题?系统内部问题,内存方面呀等等,有可能是oom,循环操作,自旋也有可能导致
————技术驱动业务,业务创造价值。

    推荐阅读