敖丙文案
HashMap
HashMap 在1.7和1.8做的比较大的改变,就是数据结构,1.7之前使用的就是 数组 + 链表,它的每个节点都是一个Entry节点(它的一个内部类)
HashMap 它的数据插入过程是使用头插法,但是HashMap 使用头插法会造成什么问题呢?
它在 resize时候,可能会造成在调用 transfer方法,把里面的一些Entry进行一个rehash,刚才这个过程当中,可能造成一个链表的循环,就可能在下一次get的时候出现一个死循环的过程。
当然,HashMap发生死锁现象也不是单单这一种情况,还比如是因为它没有锁,在多线程并发的情况下,对于它的数据就不能保证它是一个安全的,就是我put进去的值,取出来还是我put进去的值。
在1.8以后对他进行了一个比较大的一个变化,它把结构变成了一个数组 + 链表 + 红黑树的这么一个结构,把原来的Entry节点也变成了一个Node节点,它的整个put过程也做了一个优化。(尾插)
扩容机制
扩容,首先我们要了解的就是capacity这个节点就是我在初始化这个HashMap,如果没有特意设置它的capacity,它的默认初始化容量是 16 ,负载因子是 0.75,它会计算出一个threshold是一个它的阈值,就是一个扩容的阈值,如果当我put的时候我会先判断,我当前的这个size是不是要大于这个阈值,如果大于的时候,他就会新创建出来一个两倍大小,它扩容成原来的两倍,将原来的一个entry 再重新rehash的这么一个过程。
为什么使用concurrenthashmap,不是使用加锁或者是hashtable、collections.synchronized 对它进行一个同步操作?
首先cchashmap,它的并发度是更高的,而且在我看来 普通的hashtable 就是直接对里面的方法进行了一个synchronized 就是加了一个对象锁,但是cchashmap 数据结构之后变成了同样的数组 + 链表 + 红黑树,而且它只锁住,就是我当前获取到的那个node所在的节点,在上锁的时候他使用了CAS + synchronized 分段式锁,再加上 jdk1.6以后对synchronized进行了一个优化锁升级的一个过程,所以它的效率是更高的,所以它支持的并发度是更高的。
锁升级
锁升级过程就是 乐观锁比较并交换的过程,如果没有设置成功的话,它会进行一个自旋,自旋到一定的次数之后才会升级成一个synchronized这么一个重量级的锁,这样的话就保证了一个它的性能问题
事务的隔离级别有几种和传播方式
隔离级别有五种
无、Read Committed、Read Uncommitted、Repeatable、序列化
传播方式:
required 就是我支持当前事务
Supports 如果没有事务就以非事务运行
Mandatory 如果当前没有事务,我就抛异常
Not_Supported 非事务运行,如果有就挂起
Never 非事务运行,如果有抛异常
Requires_New 支持一个嵌套的一个事务过程
跟数据库的事务隔离级别有什么区别
AOP 方面
项目中使用到的AOP:在项目中主要使用的AOP有可以去做权限预热,还有一些日志的操作,因为它可以定义一些切点,将那些切点的切面动态地植入进去,方便我们去做一些权限的判断,比如说我们用JWT登录进我们的系统之前,就可以先AOP拦截下来,就判断它这个权限。
然后也可以把一些日志操作,相应的做一些变更。
AOP主要有两种框架,一个是 jdk的proxy 使用动态代理,第二个就是CGlib
JDK proxy 它主要可以生成语言接口,就是语言实现相同接口的,这么一个类,如果原来类没有实现接口的话,就是他可能不太适合。
如果GCLib的话,他就会使用一种字节码的编译器,就是ASM的一个编译器,它可以生成一个目标类的一个子类,去实现类似的就是一个代理的功能。
至于性能上来说,可能就是CGLib,他在创建对象的过程中,会获取到我泛型这么一个类型,可能就是做的更慢一些,但是在运行时,可能效率要更高一些。
刚刚提到了对反射机制有一定的了解,在开发中什么地方用到了反射, 或者说JAVA本身哪些地方用到了反射
简历里说了对现有的流程进行了一个重构,现在可以达到代码多用的效果,一套代码可以跑多个流程,那么我们如何去适配各个流程的,首先就是要用到一些泛型。
反射可以获取到运行时的一些类型(动态获取)
有没有考虑过反射的性能
据我了解反射在大多数情况下的性能并没有那么好,没错,因为反射运行的时候,它会先去方法区里面就是先看他这个类有没有加载过,如果没有加载过就会有一个类加载的过程,可能的话,就会在一定程度上影响到这个性能。
因为反射是一种解释性操作,我们需要去告诉jvm,希望他怎么做,就肯定比我们直接写代码,直接操作会慢一些。
Nginx 方向
Nginx 是怎么去做负载均衡的?或者说常见的负载均衡算法有哪些
Nginx 的负载均衡主要有几种吧
第一种就是一致性哈希就是所有的流量分配的都一样 跟HashMap处理key一样,去做一致性hash,把请求的均衡打在各个机器上。
第二种就是加权哈希,因为有些机器性能方面就是好,通过权给他加大,让更多的流量打在某个机器上面,指定轮询几率。
还有一种就是轮询,轮询的效果就没有那么好了。
http tcp,udp方面
tcp为什么是三次握手,不是四次或两次
首先,他三次握手是因为什么?我们客户端要给那个服务器报告我要和你建立连接,顺便把自己的一个发送能力,发给服务器,让服务器知道;服务器判断我是否可以和你创建连接,把我的一个接受能力返回给客户端,然后建立连接。
只有三次握手,才能保证就是双方的发送能力和接收能力都达到了一个我认为就是可行的状态,而且协议没有百分之百可靠的。其实tcp,它本身是一个稳定的可靠的链接,所以,所以三次已经够了,如果四次的话,也不能保证百分百可靠。
数据库方面
对数据库多个索引匹配有什么经验吗
如果说就是在索引这一块,其实是mysql的一个调优比较大的过程。在我看来的话,就是我们首先可以在创建索引的时候可以考虑一下几个因素
首先是覆盖索引,因为覆盖索引可以减少回表的次数,mysql5.6以后对覆盖索引进行了一个优化,就是支持一个索引下推的一个功能,(多个索引之前在服务层进行筛选,后来变成都在存储引擎层进行筛选,减少了回表的过程进行了优化)就是把我覆盖索引所覆盖的一个字段统一实写,而不是 * ,进一步的进行筛选,尽量减少回表的次数。
这个我们可以再explain看它的执行计划的时候,那个Extra 那个字段里面有Using index condition,然后我们就可以看到。
另外我们还可以对他进一步的进行优化,如果我们的存储介质使用的是机械硬盘的话,因为我们都知道机械硬盘它害怕随机读写的,因为他有一个随机读写的磁盘寻址开销,这时我们可以把 MRR打开,(Multi Range Read)然后他可以把回表之前,把我们的ID读到一个Buffer里面,进行一个排序,把原来的一个随机操作变成一个顺序操作,我觉得这是覆盖索引可以做的一些优化,因为覆盖,所以也可以避免,比如说排序用到的一些临时文件,或者可以利用最左原则和覆盖,所以配合,可以减少一些索引的维护。
针对普通索引
如果是对一些普通索引,就比如那些写多读少的服务,并且这个服务就是要求唯一性,要求没有那么高,或者我们的业务代码可以保证唯一性的时候,我们可以使用普通索引,因为普通索引可以用 Change Buffer, Change Buffer 就可以把一些写操作给缓存下来,在我们读的时候进行 merge 操作,这样的话就可以提高写入的速度,还有我们的内存的一个命中率。
索引失效
如果我们的这个索引走不上,我么你应该考虑哪些方面?
SQL写的问题
如果我们这个索引走不上,我们可以考虑第一:是不是我们的SQL 写的有问题?比如说我们对索引字段进行了一些函数操作,在连接查询的时候,编码不一样;
要不然我们就可以进一步排查,有没有可能哪些字段的类型不一样?
就比如String,付给它的一个ID,如果String跟ID比较的话,我们会把String转成ID,在mysql里面,就是运用到了一个隐式函数,一个cast函数进行转换,也有可能是这一点。
索引统计信息 Analyze table
在考虑过我们自身的 SQL以后,可以考虑是不是索引统计信息有问题?
如果是索引统计信息有问题的话 ,我们可以去 Analyze table 重新统计所有信息,这也可能是一点,因为我们知道索引信息并不是一个准确值,它是一个随机采样的过程,可能会出现问题。
业务表
还有可能就是因为我业务表可能增加太多,内存的一个空洞比较多,所以都有可能遭横我们内存的索引一个选择的问题。
Explain 分析出来的索引,它一定是最优的么?
这个是不一定的,因为它可能会选错,索引我们在索引的时候,可能涉及到回表操作和排序操作出现问题。
索引建的不好,导致查询很慢怎么办?
索引建的不好,导致索引走的很差,就查询速度慢的这种情况,如果碰到这种情况,就首先我们可以考虑,就是用那个 force index,强制走一个索引,但是这个不是太好,就是作为一个业务的应急预案。
因为它在迁移到另一个数据库里面的时候就不支持了,他还需要我们就是做一个代码的一个重新的发布,这个是不太好的;
排查并删除索引
还有一种就是我们可以考虑用覆盖索引 + 最左原则, 就是这种原则考虑,能不能把选错的删掉,这其实也还是一个比较常用的优化方案之一。
像数据库这一块,比如一些数据就是存在那种热点数据,我们要大批量更新,如何解决
这种问题,我们可以把它写在一个内存的临时表里面,因为我们知道就是 innoDB,它是会维护一个 buffer pool 的,如果我们直接把大量的一些数据全部都读进去的话,可能会造成 flush的一个操作,就是把脏页刷回 Mysql,就是这么一个操作会造成我们线上的一个业务阻塞。
就比如说我们用那个缓存的那种方式去操作,热点数据,它数据太猛的话,其实使用Redis客户端也不可能访问的到。因为 带宽打满。
数据量太猛的时候,一瞬间就是Redis瞬间被打满,访问不了 访问数据库也不行,应该怎么办?
其实有一种 谷歌guava的本地缓存。
了解过RPC框架么
RPC框架暂时没有太多了解,但是对于RPC原理能说一些
RPC的框架,就比如说我是一个服务端,我把这个端口暴露出去,发布出去,我们需要在我这个服务端启动的时候,把这个接口发布到我的一个注册中心。
而我的客户端就是consumer,在启动的时候去注册中心里面找到对应的就是他发布的一个接口,注入到我那个consumer那个服务里面,这样的话就是创建了一个stub一个代理对象,它把服务之间的通信封装成了一个对象的调用,底层涉及到了一些网络通信,还有它的一个协议的转换。
刚才提到了http调用,其实调用的话就会存在一种超时的问题(hystrix)
调用的时候超时了,怎么处理? 就是捕获异常,然后重试,重试超过了一定次数以后,就让它报错,然后放弃重试。