# 阿里巴巴面经
# mysql的事务隔离级别有什么?
- MySQL有四种隔离级别,按照隔离性从低到高分别是读未提交(read uncommitted)、读已提交(read committed)、可重复读(repeatable read)或串行化(serializable)。
- 读未提交是读取其他事务未提交的修改,存在脏读、幻读、不可重复读的问题,适合对数据一致性要求极低的场景。
- 读已提交是只能读取其他事务已提交的修改,能够解决脏读问题,但是仍有不可重复读和幻读问题。
- 可重复读是同一事务内多次读取同一数据时结果保持一致,能够解决脏读和不可重复读问题。它是MySQL InnoDB引擎的默认隔离级别,InnoDB使用MVCC和Next-Key Locks间隙锁机制可以解决幻读。
- 串行化会给记录加读写锁,能够完全解决脏读、不可重复读和幻读问题 ,但是并发性能差,适用于对数据一致性要求高的场景。
# 什么情况下不用Inno DB?
- 使用InnoDB的原因是InnoDB支持事务、行锁以及外键等,当业务没有事务和并发更新需求时,可以考虑使用其他引擎。比如有大量读业务时可以使用MyISAM,需要临时存储高频访问的数据时可以使用Memory引擎。当有场景使用InnoDB不支持的引擎时也不可以用InnoDB,比如需要存储地理信息、空间索引查询的时候,选择Spatial引擎。
# 常见的索引失效场景有哪些?
- 对索引使用模糊查询以%开头时,进行左/左右模糊匹配-,会造成索引失效。因为B+树索引是按照前缀顺序存储的,无法通过前缀匹配定位索引位置。
- 对索引字段使用函数或者对索引进行表达式计算会导致索引失效,索引保存的是字段的原始值,而不是计算后的值;在MySQL8.0后可以给函数计算后的值建立一个索引,可以扫描索引查询数据。
- 对索引进行隐式类型转换会使索引失效,如果索引字段是字符串类型,输入参数是整型时索引会失效。因为MySQL在遇到字符串和数字比较的时候会自动把字符串转换成数字,相当于给索引使用了类型转换函数,破坏了索引的原始值而导致失效。
- 联合索引不遵循最左匹配原则会导致部分索引失效,因为联合索引数据会按照索引第一列排序,第一列数据相同才会按照第二列排序,必须从最左侧开始匹配,否则无法定位。
- WHERE子句如果有OR,OR的其中一侧没有索引就会导致索引失效,有条件不是索引列就会进行全表扫描。 使用!=/<>/NOT IN或NOT EXISTS可能导致索引失效,因为否定操作需要匹配大部分数据,MySQL认为全表扫描比走索引更高效,会放弃索引。
# 字符集会不会影响索引?
- 当数据库的字符集和查询中使用的字符集不一致的时候,导致数据在比较前需要转换字符集,会破坏索引的原有排序逻辑,使得索引失效;
- 索引会按照字段的排序规则维护有序结构,字符集不同则字段的排序规则不同,会导致排序异常,影响准确性。
# 进程和线程的区别?
- 最核心的差异是进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位。
- 进程内可以有多个线程,进程的结束会终止所有线程。没有线程的进程可以看做单线程,所以线程也被称为轻量级进程。
- 线程崩溃可能导致整个进程崩溃,但是进程崩溃不影响其他进程。
- 在开销方面,进程有独立的代码和数据空间,切换会有较大开销;同一类线程共享代码和数据空间,有独立的运行栈和程序计数器,切换开销小。
# 有哪些常见的锁?
- 从锁获取机制来看,有乐观锁和悲观锁。乐观锁是假设并发访问不会发生冲突,操作时不加锁,更新数据使用版本号/CAS操作尝试更新;悲观锁会对每次操作加锁,假设并发一定有冲突,例如synchronized。
- 从锁的竞争策略来看,有公平锁和非公平锁。公平锁会按照线程申请锁的顺序获取锁,先等待先获得,公平,但是存在线程唤醒开销;非公平锁则允许“插队”,吞吐量高,可能会造成线程饥饿现象。使用ReentrantLock可以对公平性进行设置。
- 从锁的竞争强度来看,有偏向锁、轻量级锁、重量级锁。偏向锁是对synchronized的优化,只有一个线程访问时后续无需重复加锁;偏向锁被竞争时会升级成为轻量级锁,其他线程通过自旋尝试获取锁,不会阻塞;自旋失败一定次数后升级为重量级锁,使其他线程阻塞;
- 从锁的功能特性看,有可重入锁、读写锁、自旋锁和互斥锁。可重入锁是线程重复获取已持有的锁,比如synchronized和ReentrantLock避免锁死;读写锁允许多个读线程并发访问,其余操作互斥,也就是ReentrantReadWriteLock;自旋锁是线程通过循环CAS尝试获取锁;互斥锁会通过互斥机制保证同一时间只有一个线程持有锁。
- 从持有锁的方式,看有独享锁和共享锁。独享锁使同一时间只有一个线程持有锁;共享锁允许多个线程同时持有锁,线程间不互斥。
- 从设计思想来看,又可以分为分段锁和行级锁/表级锁。分段锁将资源分段,给每段资源加锁以降低竞争;行级锁、表级锁则是数据库中按照锁定范围的划分。
# synchronized的具体实现?
- synchronized是Java提供的内置锁,依赖JVM层面的锁机制实现,主要通过对象头、和监视器锁实现同步,并且配合锁升级机制提升性能。
- 通过Java对象的对象头Mark Word可以判断当前锁的状态。如果是偏向锁,Mark Work会记录线程ID,后续线程可直接复用锁;如果是轻量级锁,线程使用CAS将MarkWork替换成自己的锁记录指针,其余线程自旋重试,不阻塞;如果是重量级锁,会变成Monitor监视器锁,MarkWord是Monitor地址,其余线程会进入阻塞状态。
- 监视器锁的实现原理是给每个对象关联一个Monitor,包含当前持有锁的线程owner、等待队列WaitSet和阻塞队列EntryList,以及锁的计数器。owner会指向执行中的线程,当线程调用wait()方法时,计数器归零,线程进入等待队列,等待被唤醒后进入阻塞队列,随后唤醒阻塞队列的线程获取Monitor并执行下一个线程。

# 了解分布式锁吗?
- 在分布式架构下,服务部署在多个节点,本地只能控制单个JVM的线程,无法阻止其他节点访问共享资源。分布式锁是可以解决分布式系统多节点并发竞争共享资源的工具,分布式锁相当于全局锁,能够跨节点控制,实现同一时间只有一个节点持有锁,且节点崩溃时会释放锁从而避免死锁,解决并发问题。
# 常见的分布式锁实现?
- 实现分布式锁最常用的是通过Redis实现,还可以基于ZooKeeper实现和数据库实现。
- 利用Redis的SET key value NX PX timeout命令可以实现分布式锁,其中NX保证互斥,PX设置过期时间防止死锁,锁释放的时候使用Lua脚本保证原子性删除,防止误删他人锁。如果涉及到主从同步可以使用RedLock算法,防止锁丢失。如果希望实现操作未完成时锁可以自动延长时间,可以使用基于Redission的分布式锁,它的看门狗机制可以实现每隔一段时间进行判断并续期。
- 还可以通过ZooKeeper实现,利用临时顺序节点和事件监听器。客户端获取锁就是在持久节点下创建一个临时顺序节点,如果该临时顺序节点序号最小则成功获取锁,否则会在前一个节点上设置事件监听器等待通知,避免自旋;释放锁或节点故障时就会删除这个子节点,监听器通知下一个节点获取锁。
# 讲讲JVM的内存结构?
- JVM内存结构(运行时数据区)可以分为Java虚拟机栈、堆、方法区、本地方法栈和程序计数器五个部分。在JDK8之前,方法区通过永久代实现,JDK8及以后,永久代被元空间取代。
- 其中堆和方法区是线程共享的,虚拟机栈、程序计数器和本地方法栈是线程私有的。
- Java虚拟机栈存储方法执行时的栈帧,用来存储局部变量、操作数栈动态链接和返回地址,每个线程的虚拟机栈生命周期和线程相同;堆存放对象的实例和数组,是垃圾回收的主要区域;本地方法栈登记本地方法,管理本地方法的调用;程序计数器记录当前线程执行的字节码指令地址;方法区保存已经被加载的类信息、静态变量和即时编译后的代码等数据,关闭JVM后释放。

# 怎么理解垃圾回收?
- 垃圾回收就是JVM进行自动管理内存的机制,识别并回收不再被使用的对象,释放内存资源。
- 主要回收的区域是堆内存,回收不再被存活线程引用的对象,回收的同时减少内存碎片。
- 垃圾回收器通过可达性分析算法判断对象是否可以被回收,然后选择合适的算法回收对象,常见的算法有复制算法,标记-清除算法,标记整理算法。一般来说在新生代使用复制算法,在老年代使用标记清除算法和标记整理算法。

# 垃圾回收怎么判断清除?
- 判断对象是否可以被回收有引用计数法和可达性分析算法两种方法。其中可达性分析算法比较常用,能够避免出现循环引用导致无法回收的问题。
- 引用计数法:
- 给对象中添加引用计数器,有地方引用该对象计数器加一,引用失效时计数器减一。如果计数器为0则说明对象不再可用。
- 如果两个对象互相引用,会导致计数器无法为0,无法进行垃圾回收。
- 可达性分析算法:
- 设置GC Roots对象,如果一个对象到GC Roots没有任何引用链相连,则该对象不可用,需要被回收。可以避免使用引用计数器产生的循环引用对象不可回收的问题。
- GC Roots对象是垃圾回收器可直接访问的起点,且对象本身不能被回收。 可以作为GC Roots的对象包括虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象和被同步锁(synchronized)持有的对象以及JNI中引用的对象等。
- 引用计数法:

# TCP三次握手过程?两次和四次不能吗?
- 使用TCP前必须先通过三次握手建立连接,使客户端和服务器双方确认彼此的发送和接收能力。
- 建立连接的过程可以概况为请求、应答和确认三个阶段。客户端和服务端都处于CLOSE状态,第一次握手是客户端向服务端发送一个SYN报文,同步序列编号x,客户端进入SYN_SENT状态;第二次握手是服务端收到SYN报文后确认客户端发送能力正常,回复SYN+ACK报文,同步序列编号y,确认号为x+1,服务器进入SYN_RCVD状态;第三次握手是客户端收到SYN+ACK报文后,确认服务器的发送和接收能力正常,回复ACK报文。确认号为y+1,随后客户端进入ESTABLISHED状态,服务器收到ACK后也进入ESTABLISHED状态,连接建立成功。
- 如果使用两次握手,服务端在第二次握手后会直接变成ESTABLISHED状态,如果此时客户端因为延迟而重发SYN报文,会导致服务端建立历史连接而产生资源浪费。
- 如果使用四次握手也能可靠连接,但是三次握手可以将四次握手的第二步和第三步合并成一步,所以不需要多通信一次。
# 解释分布式系统中出现一致性问题的原因?
- 分布式系统中有多个节点,节点之间可能会因为网络延迟、宕机、分区等故障导致数据不一致,就会产生一致性问题。
- 如果一个节点宕机,会导致该节点和其他节点不同步;出现网络问题时可能会导致节点之间无法传递数据;当多个客户端向不同节点写入数据时,可能会导致数据覆盖或计算错误。
# 强一致性、线性一致性、弱一致性、因果一致性?
- 强一致性是数据更新后所有节点同时看到最新值,一致性强,但是性能较差。
- 线性一致性在强一致性的基础上要求操作的执行顺序和全局时钟下的真实时间顺序一致,把所有操作按照时间先后执行,即时更新。实现成本高,适合金融以及电商场景。
- 因果一致性保证有因果关系的操作满足一致性,比如下单+支付两个操作具有因果关系,强调操作顺序,约束性弱于强一致性。
- 弱一致性无法保证节点即时更新,只能保证最终会同步,一致性最弱,性能和可用性最高,适合搜索引擎。
# 讲讲Raft?Raft怎么解决?
- Raft算法是经典的分布一致性算法,实现多节点状态机的高可靠一致性。
- Raft算法将节点划分为领导者Leader、跟随者Follower和候选人Candidate三个角色,核心思想是Leader节点接收来自客户端的日志信息,同步给集群中的其他节点,收到其他节点的成功响应为成功同步,同步半数以上节点时视为成功,再进行提交。
- 初始状态所有节点都是Follower,跟随者会定期从领导者处接收心跳信息确认其存活,如果一定时间内没有收到消息,即任期超时时,跟随者会转变为候选人,向其他节点发送投票请求。每个节点在一个任期只能投一票给日志更新最完整的候选人,票数超过半数节点会成为新Leader,没有则重新选举。
- Leader包含所有已经提交的日志条目,且具有任期号,如果有任期号小于当前Leader的旧Leader节点复活则自动转为Follower,避免出现双Leader。
评论
验证登录状态...