蚂蚁金服一面:十道经典面试题解析
源 / 文/ 捡田螺的小男孩
前言
最近编程讨论群有位小伙伴去蚂蚁金服面试了,以下是面试的真题,跟大家一起来讨论怎么回答。
点击上方“捡田螺的小男孩”,选择“设为星标”,干货不断满满
1. 用到分布式事务嘛?为什么用这种方案,有其他方案嘛?
什么是分布式事务
谈到事务,我们就会想到数据库事务,很容易就想到原子性、一致性、持久性、隔离性。
分布式事务跟数据库事务有点不一样,它是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单来说,分布式事务指的就是分布式系统中的事务,它的存在就是为了保证不同数据库节点的数据一致性。
分布式事务基础
分布式事务需要需要知道CAP理论和BASE理论。
CAP理论
一致性(C:Consistency):一致性是指数据在多个副本之间能否保持一致的特性。例如一个数据在某个分区节点更新之后,在其他分区节点读出来的数据也是更新之后的数据。
可用性(A:Availability):可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。这里的重点是"有限时间内"和"返回结果"。
分区容错性(P:Partition tolerance):分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务。
一个分布式系统中,CAP理论它只能同时满足(一致性、可用性、分区容错性)中的两点。
BASE 理论
BASE 理论, 是对CAP中AP的一个扩展,对于我们的业务系统,我们考虑牺牲一致性来换取系统的可用性和分区容错性。BASE是Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性)三个短语的缩写。
基本可用是指,通过支持局部故障而不是系统全局故障来实现的;
Soft State表示状态可以有一段时间不同步;
最终一致,最终数据是一致的就可以了,而不是实时保持强一致。
分布式事务的几种解决方案
2PC(二阶段提交)方案,事务的提交分为两个阶段:准备阶段和提交执行方案。
TCC(即Try、Confirm、Cancel),它采用了补偿机制,核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
本地消息表,它的核心思想就是将分布式事务拆分成本地事务进行处理。
最大努力通知,实现最大努力通知,可以采用MQ的ack机制。
Saga事务,它的核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。
业界目前使用本地消息表这种方案是比较多的,它的核心思想就是将分布式事务拆分成本地事务进行处理。可以看一下基本的实现流程图吧:
对于消息发送方:
首先需要有一个消息表,记录着消息状态相关信息。
业务数据和消息表在同一个数据库,即要保证它俩在同一个本地事务。
在本地事务中处理完业务数据和写消息表操作后,通过写消息到MQ消息队列。
消息会发到消息消费方,如果发送失败,即进行重试。
消息消费方:
处理消息队列中的消息,完成自己的业务逻辑。
此时如果本地事务处理成功,则表明已经处理成功了。
如果本地事务处理失败,那么就会重试执行。
如果是业务上面的失败,给消息生产方发送一个业务补偿消息,通知进行回滚等操作。
生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
2.JDK6、7、8分别提供了哪些新特性
JDK 6 新特性
Desktop类(它允许一个Java应用程序启动本地的另一个应用程序去处理URI或文件请求)
使用JAXB2来实现对象与XML之间的映射
轻量级 Http Server API
插入式注解处理API(lombok框架基于这个特性实现)
STAX(是JDK6中一种处理XML文档的API)
JDK 7的新特性
switch 支持String字符串类型
try-with-resources,资源自动关闭
整数类型如(byte,short,int,long)能够用二进制来表示
数字常量支持下划线
泛型实例化类型自动推断,即”<>”
一个catch中捕获多个异常类型,用(|)分隔开
增强的文件系统
Fork/join 框架
JDK8 的新特性
lambada表达式
函数式接口
方法引用
默认方法
Stream API
Optional
Date Time API(如LocalDate)
重复注解
Base64
JVM的新特性(如元空间Metaspace代替持久代)
3. https原理,工作流程
HTTPS = HTTP + SSL/TLS,即用SSL/TLS对数据进行加密和解密,Http进行传输。
SSL,即Secure Sockets Layer(安全套接层协议),是网络通信提供安全及数据完整性的一种安全协议。
TLS,即Transport Layer Security(安全传输层协议),它是SSL3.0的后续版本。
客户端发起Https请求,连接到服务器的443端口。
服务器必须要有一套数字证书(证书内容有公钥、证书颁发机构、失效日期等)。
服务器将自己的数字证书发送给客户端(公钥在证书里面,私钥由服务器持有)。
客户端收到数字证书之后,会验证证书的合法性。如果证书验证通过,就会生成一个随机的对称密钥,用证书的公钥加密。
客户端将公钥加密后的密钥发送到服务器。
服务器接收到客户端发来的密文密钥之后,用自己之前保留的私钥对其进行非对称解密,解密之后就得到客户端的密钥,然后用客户端密钥对返回数据进行对称加密,酱紫传输的数据都是密文啦。
服务器将加密后的密文返回到客户端。
客户端收到后,用自己的密钥对其进行对称解密,得到服务器返回的数据。
4. 讲讲java jmm volatile的实现原理
volatile关键字是Java虚拟机提供的的最轻量级的同步机制,它作为一个修饰符,用来修饰变量。它保证变量对所有线程可见性,禁止指令重排,但是不保证原子性。
volatile是如何保证可见性的呢?我们先来看下java内存模型(jmm)
Java虚拟机规范试图定义一种Java内存模型,来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台上都能达到一致的内存访问效果。
为了更好的执行性能,java内存模型并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存打交道,也没有限制编译器进行调整代码顺序优化。所以Java内存模型会存在缓存一致性问题和指令重排序问题的。
Java内存模型规定所有的变量都是存在主内存当中,每个线程都有自己的工作内存。这里的变量包括实例变量和静态变量,但是不包括局部变量,因为局部变量是线程私有的。
线程的工作内存保存了被该线程使用的变量的主内存副本,线程对变量的所有操作都必须在工作内存中进行,而不能直接操作操作主内存。并且每个线程不能访问其他线程的工作内存。
volatile变量,保证新值能立即同步回主内存,以及每次使用前立即从主内存刷新,所以我们说volatile保证了多线程操作变量的可见性。
指令重排是指在程序执行过程中,为了提高性能, 编译器和CPU可能会对指令进行重新排序。volatile是如何禁止指令重排的?在Java语言中,有一个先行发生原则(happens-before)
程序次序规则:在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。
管程锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
线程终止规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
传递性:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
实际上volatile保证可见性和禁止指令重排都跟内存屏障有关。我们来看一段volatile使用的demo代码
public class Singleton {
private volatile static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
编译后,对比有volatile关键字和没有volatile关键字时所生成的汇编代码,发现有volatile关键字修饰时,会多出一个lock addl $0x0,(%esp),即多出一个lock前缀指令,lock指令相当于一个「内存屏障」
lock指令相当于一个内存屏障,它保证以下这几点:
1.重排序时不能把后面的指令重排序到内存屏障之前的位置
2.将本处理器的缓存写入内存
3.如果是写入动作,会导致其他处理器中对应的缓存无效。
第2点和第3点就是保证volatile保证可见性的体现嘛,第1点就是禁止指令重排列的体现。内存屏障又是什么呢?
内存屏障四大分类:(Load 代表读取指令,Store代表写入指令)
内存屏障类型
抽象场景
描述
LoadLoad屏障 | Load1; LoadLoad; Load2 | 在Load2要读取的数据被访问前,保证Load1要读取的数据被读取完毕。 |
StoreStore屏障 | Store1; StoreStore; Store2 | 在Store2写入执行前,保证Store1的写入操作对其它处理器可见 |
LoadStore屏障 | Load1; LoadStore; Store2 | 在Store2被写入前,保证Load1要读取的数据被读取完毕。 |
StoreLoad屏障 | Store1; StoreLoad; Load2 | 在Load2读取操作执行前,保证Store1的写入对所有处理器可见。 |
为了实现volatile的内存语义,Java内存模型采取以下的保守策略
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。
有些小伙伴,可能对这个还是有点疑惑,内存屏障这玩意太抽象了。我们照着代码看下吧:
内存屏障保证前面的指令先执行,所以这就保证了禁止了指令重排啦,同时内存屏障保证缓存写入内存和其他处理器缓存失效,这也就保证了可见性,哈哈~
5. 讲一讲7层网络模型,tcp的为什么要三次握手
计算机网路体系结构有三层:OSI七层模型、TCP/IP四层模型、五层体系结构,如图:
七层模型,亦称OSI(Open System Interconnection),国际标准化组织(International Organization for Standardization)制定的一个用于计算机或通信系统间互联的标准体系。
应用层:网络服务与最终用户的一个接口,常见的协议有:HTTP FTP SMTP SNMP DNS.
表示层:数据的表示、安全、压缩。,确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。
会话层:建立、管理、终止会话,对应主机进程,指本地主机与远程主机正在进行的会话.
传输层:定义传输数据的协议端口号,以及流控和差错校验,协议有TCP UDP.
网络层:进行逻辑地址寻址,实现不同网络之间的路径选择,协议有ICMP IGMP IP等.
数据链路层:在物理层提供比特流服务的基础上,建立相邻结点之间的数据链路。
物理层:建立、维护、断开物理连接。
6.说说线程池的工作原理
面试官如果要我们讲下线程池工作原理的话,大家讲下以下这个流程图就可以啦:
为了形象描述线程池执行,加深大家的理解,我打个比喻:
核心线程比作公司正式员工
非核心线程比作外包员工
阻塞队列比作需求池
提交任务比作提需求
当产品提个需求,正式员工(核心线程)先接需求(执行任务)
如果正式员工都有需求在做,即核心线程数已满),产品就把需求先放需求池(阻塞队列)。
如果需求池(阻塞队列)也满了,但是这时候产品继续提需求,怎么办呢?那就请外包(非核心线程)来做。
如果所有员工(最大线程数也满了)都有需求在做了,那就执行拒绝策略。
如果外包员工把需求做完了,它经过一段(keepAliveTime)空闲时间,就离开公司了。
7.你们数据库的高可用是怎么实现的?
高可用,即High Availability,是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。单机部署谈不上高可用,因为单点故障问题。高可用都是多个节点的,我们在考虑MySQL数据库的高可用的架构时,需要考虑这几个方面:
如果数据库节点宕机,需要尽快回复,保证业务不受宕机影响。
从数据库节点的数据,尽可能跟主节点数据实时保持一致,至少保证最终一致性。
数据库节点切换时,数据不能缺失。
7.1 主从或主主半同步复制
用双节点数据库,搭建单向或者双向的半同步复制。架构如下:
通常会和proxy、keepalived等第三方软件同时使用,即可以用来监控数据库的健康,又可以执行一系列管理命令。如果主库发生故障,切换到备库后仍然可以继续使用数据库。
这种方案优点是架构、部署比较简单,主机宕机直接切换即可。缺点是完全依赖于半同步复制,半同步复制退化为异步复制,无法保证数据一致性;另外,还需要额外考虑haproxy、keepalived的高可用机制。
7.2 半同步复制优化
半同步复制机制是可靠的,可以保证数据一致性的。但是如果网络发生波动,半同步复制发生超时会切换为异步复制,异复制是无法保证数据的一致性的。因此,可以在半同复制的基础上优化一下,尽可能保证半同复制。如双通道复制方案
优点:这种方案架构、部署也比较简单,主机宕机也是直接切换即可。比方案1的半同步复制,更能保证数据的一致性。
缺点:需要修改内核源码或者使用mysql通信协议,没有从根本上解决数据一致性问题。
7.3 高可用架构优化
保证高可用,可以把主从双节点数据库扩展为数据库集群。Zookeeper可以作为集群管理,它使用分布式算法保证集群数据的一致性,可以较好的避免网络分区现象的产生。
优点:保证了整个系统的高可用性,扩展性也较好,可以扩展为大规模集群。
缺点:数据一致性仍然依赖于原生的mysql半同步复制;引入Zookeeper使系统逻辑更复杂。
7.4 共享存储
★
优点:部署简单,价格合适,保证数据的强一致性 缺点:对IO性能影响较大,从库不提供读操作
7.5 分布式协议
优点:不依赖于第三方软件,可以实现数据的强一致性; 缺点:配置较复杂;需要使用NDB储存引擎;至少三节点;
8. 读写分离的场景下,怎么保证从数据库读到最新的数据?
写请求是直接写主库,然后同步数据到从库 读请求一般直接读从库,除飞强制读主库
A发起写请求,更新主库数据,并在缓存中设置一个标记,表示数据已更新,标记格式为:userId+业务Id。 设置此标记,设置过期时间(估值为主库和从库同步延迟的时间) B发起读请求,先判断此请求,在缓存中有没有更新标记。 如果存在标记,走主库;如果没有,请求走从库。
9. 如何保证MySQL数据不丢?
binlog日志
statement:每一条会修改数据的sql都会记录到binlog中,不建议使用。 row:基于行的变更情况记录,会记录行更改前后的内容,推荐使用。 mixed:混合statement和row两个模式,不建议使用。
write:指把日志写到文件系统的page cache,并没有把数据持久化到磁盘,因此速度较快。 fsync,实际的写盘操作,即把数据持久化到磁盘。
redo log日志
物理上是在MySQL进程内存中,存在redo log buffer中, 物理上在文件系统的page cache里,写到磁盘 (write),但是还没有持久化(fsync)。 存在hard disk,已经持久化到磁盘。
设置为0时,表示每次事务提交时都只是把redo log留在redo log buffer 中 ; 设置为1时,表示每次事务提交时都将 redo log 直接持久化到磁盘; 设置为2时,表示每次事务提交时都只是把redo log 写到page cache。
10. 高并发下如何设计秒杀系统?
页面静态化 按钮至灰控制 服务单一职责 秒杀链接加盐 限流 分布式锁 MQ异步处理 限流&降级&熔断
为了防止某个用户请求过于频繁,我们可以对同一用户限流; 为了防止黄牛模拟几个用户请求,我们可以对某个IP进行限流; 为了防止有人使用代理,每次请求都更换IP请求,我们可以对接口进行限流。 为了防止瞬时过大的流量压垮系统,还可以使用阿里的Sentinel、Hystrix组件进行限流。
if(jedis.set(key_resource_id, uni_request_id, "NX", "EX", 100s) == 1){ //加锁
try {
do something //业务处理
}catch(){
}
finally {
//判断是不是当前线程加的锁,是才释放
if (uni_request_id.equals(jedis.get(key_resource_id))) {
jedis.del(lockKey); //释放锁
}
}
}
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end;
限流,就是限制请求,防止过大的请求压垮服务器; 降级,就是秒杀服务有问题了,就降级处理,不要影响别的服务; 熔断,服务有问题就熔断,一般熔断降级是一起出现。
参考资料
五大常见的MySQL高可用方案: https://zhuanlan.zhihu.com/p/25960208
[2]读写分离数据库如何保持数据一致性: https://blog.csdn.net/baidu_36161424/article/details/107712388
[3]《我们一起进大厂》系列-秒杀系统设计: https://juejin.cn/post/6844903999083151374#heading-11
[4]《极客时间:MySQL45讲实战》: http://gk.link/a/10vPr
[5]MySQL是如何保证不丢数据的(一): https://cloud.tencent.com/developer/article/1674625
好文推荐
字节跳动小组长无意中得知整个部门的薪资!自己只有28K!手下人却拿35K!怎么办?
某程序员求助:盒马优选面试通过,却被告知在阿里黑名单里,无法入职!自己只用过花呗借呗,都按时还款,为什么会进入黑名单?
知乎高赞:一个程序员的水平能差到什么程度?
END
顶级程序员:topcoding
做最好的程序员社区:Java后端开发、Python、大数据、AI
一键三连「分享」、「点赞」和「在看」