美团点评2020校招系统开发方向笔试题

第一题:

造成cpu load过高的原因: Full gc次数的增大、代码中存在Bug(例如死循环、正则的不恰当使用等)都有可能造成cpu load 增高。
1. jps -v:查看java进程号
2. top -Hp [java进程号]:查看当前进程下最耗费CPU的线程
3. printf "%x\n" [步骤2中的线程号]:得到线程的16进制表示
4. jstack [java进程号] | grep -A100 [步骤3的结果]:查看线程堆栈,定位代码行。参考:如何使用JStack分析线程状态

第二题:

1) MySQL联合索引遵循最左前缀匹配规则,即从联合索引的最左列开始向右匹配,直到遇到匹配终止条件。例如联合索引(col1, col2, col3), where条件为col1=`a` AND col2=`b`可命中该联合索引的(col1,col2)前缀部分, where条件为col2=`b` AND col3=`c`不符合最左前缀匹配,不能命中该联合索引。

2) 匹配终止条件为范围操作符(如>, <, between, like等)或函数等不能应用索引的情况。例如联合索引(col1, col2, col3), where条件为col1=`a` AND col2>1 AND col3=`c`, 在col2列上为范围查询,匹配即终止,只会匹配到col1,不能匹配到(col1, col2, col3).

3) where条件中的顺序不影响索引命中。例如联合索引(col1, col2, col3), where条件为col3=`c` AND col2=b AND col1=`a`, MySQL优化器会自行进行优化,可命中联合索引(col1, col2, col3).

第三题:

1、什么是分布式事务?

答:指一次大的操作由不同的小操作组成的,这些小的操作分布在不同的服务器上,分布式事务需要保证这些小操作要么全部成功,要么全部失败。从本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

2、分布式事务产生的原因?

2.1 数据库分库分表

   当数据库单表数据达到千万级别,就要考虑分库分表,那么就会从原来的一个数据库变成多个数据库。例如如果一个操作即操作了01库,又操作了02库,而且又要保证数据的一致性,那么就要用到分布式事务。

2.2 应用SOA化

   所谓的SOA化,就是业务的服务化。例如电商平台下单操作就会产生调用库存服务扣减库存和订单服务更新订单数据,那么就会设计到订单数据库和库存数据库,为了保证数据的一致性,就需要用到分布式事务。

总结:其实上面两种场景,归根到底是要操作多数据库,并且要保证数据的一致性,而产生的分布式事务的。

分布式事务解决方案

1、两阶段提交(2PC)

XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、Mysql等数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交回滚。

优点:二阶段提交看起来确实能够提供原子性的操作

缺点:

1、同步阻塞问题:执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。

2、单点故障:由于(事务管理器)协调者的重要性,一旦协调者发生故障。(本地资源管理器)参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)

3、数据不一致:在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这会导致只有一部分参与者接收到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器无法执行事务提交。于是整个分布式系统便出现了数据不一致的现象。

4、二阶段无法解决的问题:参与者在发出commit消息之后宕机,而唯一接收到这条消息的协调者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交了。

3.2 三阶段提交(3PC)

  3PC其实在2PC的基础上增加了CanCommit阶段,是2PC的变种,并引入了超时机制。一旦事务参与者迟迟没有收到协调者的Commit请求,就会自动进行本地commit,这样相对有效的解决了协调者单点故障的问题。但是,性能和数据一致性问题没有根本解决。

3PC分为三个阶段:CanCommit、PreCommit、DoCommit

CanCommit阶段

它跟2PC的 准备阶段很像,协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。事务询问:协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应响应反馈:参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则返回No

PreCommit阶段

协调者根据参与者的响应情况来决定是否可以进行事务的PreCommit操作。根据响应情况,有以下两种可能:假如协调者从所有的参与者获得的反馈都是Yes,那么就会执行事务的与执行。发送预提交请求:协调者向参与者发送PreCommit请求,并进入Prepared阶段。事务预提交:参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。响应反馈:如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。假如有任何一个参与者向协调者发送了No响应,或者等待超时,或者协调者都没有接到参与者的响应,那么就执行事务的中断。发送中断请求:协调者向所有参与者发送abort请求。中断事务:参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

doCommit阶段

该阶段进行真正的事务提交,也可以分为以下两种情况:执行提交发送提交请求:协调接收到参与者发送的ACK响应,那么将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。事务提交:参与者接收到doCommit请求之后,执行正式的事务提交,并在完成事务提交之后释放所有事务资源。响应反馈:事务提交完之后,向协调者发送ACK响应。完成事务:协调者接收到所有参与者的ACK响应之后,完成事务。中断事务协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。发送中断请求:协调者向所有参与者发送abort请求事务回滚:参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。反馈结果:参与者完成事务回滚之后,像协调者发送ACK消息。中断事务:协调者接收到参与者反馈的ACK消息之后,执行事务的中断。

优点:相对于2PC而言,3PC对于协调者和参与者都设置了超时时间,而2PC只有协调者才拥有超时时间机制。这个优化解决了,参与者在长时间无法与协调者节点通讯的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。

缺点:但是仍然没有解决数据一致性问题,即在参与者收到PreCommit请求后等待最终指令,如果此时协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。

3、补偿事务(TCC)

TCC(Try-Confirm-Cancel)又称补偿事务。它实际上与2PC、3PC一样,都是分布式事务的一种实现方案而已。它分为三个操作:Try阶段:主要是对业务系统做检测及资源预留。Confirm阶段:确认执行业务操作。Cancel阶段:取消执行业务操作。

TCC事务的处理流程与2PC两阶段提交类似,不过2PC通常都是在DB层面,而TCC本质上就是应用层面的2PC,需要通过业务逻辑来实现。它的优势在于,可以让应用自己定义数据库操作的粒度,使得降低锁冲突、提交吞吐量。

不过对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作。

TCC原理图如下:

4、消息事务+最终一致性

所谓的消息事务就是基于消息中间件的两阶段提交,本质上是中间件的一种特殊利用,他是将本地事务和发消息放在一个分布式事务里,保证要么本地操作成功并且对外发消息成功,要么两者都失败,开源的RocketMQ就支持这一特性,具体原理如下:

步骤如下:服务A向消息中间件发送一条预备消息。消息中间件保存预备消息并返回成功。服务A执行本地事务。服务A发送提交消息给消息中间件,服务B接收到消息之后执行本地事务。

基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(服务A的本地操作+发消息)+服务B的本地操作,其中服务B的操作由消息驱动,只要消息事务成功,那么服务A一定成功,消息也一定发出来了,这时候服务B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到服务B操作成功,这样就变相地实现了A与B的分布式事务。

以上几个步骤可能存在异常情况,现在对其进行分析:步骤一出错:则整个事务失败,不会执行服务A的本地操作。步骤二出错:则整个事务失败,不会执行服务A的本地操作。步骤三出错:需要做回滚预备消息,由服务A实现一个消息中间件的回调接口,消息中间件会不断执行回调接口,检查服务A事务执行是否执行成功,如果失败则回滚预备消息。步骤四出错:这个时候服务A的本地事务是成功的,但是消息中间件不需要回滚,其实通过回调接口,消息中间件能够检查到服务A执行成功了,这个时候其实不需要服务发提交消息了,消息中间件可以自己对消息进行提交,从而完成整个消息事务。

第四题:

1) 客户端向服务器发起HTTPS请求,连接到服务器的443端口;

2) 服务器端有一个密钥对,即公钥(即数字证书)和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人;

3) 服务器将自己的公钥发送给客户端;

4) 客户端收到服务器端的公钥之后,检查其合法性,如果发现发现公钥有问题,那么HTTPS传输就无法继续,如果公钥合格,则客户端会生成一个客户端密钥,然后用服务器的公钥对客户端密钥进行非对称加密成密文,至此,HTTPS中的第一次HTTP请求结束;

5) 客户端发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器;

6) 服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文;

7) 然后服务器将加密后的密文发送给客户端;

8) 客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。

第五题:

1) 事务传播用于描述当一个由事务传播行为修饰的方法被嵌套入另外一个方法时,事务如何传播。常用于定义发生事务嵌套时,如何继续执行。

2) Spring 中共定义了7中事务传播类型,明细如下表, 需答出3~4种常见类型即可:

    a) PROPAGATION_REQUIRED: 当前没有事务时开启新事务,如果有则加入;

    b) PROPAGATION_REQUIRES_NEW: 强制开启新事务,挂起已有事务(如有);

    c) PROPAGATION_SUPPORTS: 当前有事务时加入, 没有则以非事务方式执行;

    d) PROPAGATION_NOT_SUPPORTED: 以非事务方式执行, 挂起当前事务(如有);

3) 可以在注解或者XML中指定传播类型, 如 “@Transactional(Propagation=xxx)”

spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。

① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘

③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

第六题:

1、 Reactor被动的等待指示事件的到来并作出反应,有一个等待的过程,做什么都要先放入到监听事件集合中等待handler可用时再进行操作,实现相对简单,对于耗时短的处理场景比较高效,但Reactor处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理。

2、Proactor直接调用异步读写操作,调用完后立刻返回,实现了一个主动的事件分离和分发模型;这种设计允许多个任务并发的执行,从而提高吞吐量;并可执行耗时长的任务(各个任务间互不影响),Proactor性能更高,能够处理耗时长的并发场景,但Proactor实现逻辑复杂;依赖操作系统对异步的支持,目前实现了纯异步操作的操作系统少,实现优秀的如windows IOCP,但由于其windows系统用于服务器的局限性,目前应用范围较小;而Unix/Linux系统对纯异步的支持有限,应用事件驱动的主流还是通过select/epoll来实现

第七题:

import java.math.BigInteger;
import java.util.*;
public class Main {
 
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        String num1 = input.nextLine();
        num1=num1.substring(1,num1.length()-1);
        String num2 = input.nextLine();
        num2=num2.substring(1,num2.length()-1);
        BigInteger add=new BigInteger(num1).add(new BigInteger(num2));
        System.out.println("\""+add+"\"");
    }
}

第八题:

方法一:从中心往两侧延伸【通过】
思路

在长度为 N 的字符串中,可能的回文串中心位置有 2N-1 个:字母,或两个字母中间。

从每一个回文串中心开始统计回文串数量。回文区间 [a, b] 表示 S[a], S[a+1], ..., S[b] 是回文串,根据回文串定义可知 [a+1, b-1] 也是回文区间。

算法

对于每个可能的回文串中心位置,尽可能扩大它的回文区间 [left, right]。当 left >= 0 and right < N and S[left] == S[right] 时,扩大区间。此时回文区间表示的回文串为 S[left], S[left+1], ..., S[right]。

class Solution {
    public int countSubstrings(String S) {
        int N = S.length(), ans = 0;
        for (int center = 0; center <= 2*N-1; ++center) {
            int left = center / 2;
            int right = left + center % 2;
            while (left >= 0 && right < N && S.charAt(left) == S.charAt(right)) {
                ans++;
                left--;
                right++;
            }
        }
        return ans;
    }
}

猜你喜欢

转载自blog.csdn.net/niuxikun/article/details/104911620