服务端开发之Java秋招面试11

努力了那么多年,回头一望,几乎全是漫长的挫折和煎熬。对于大多数人的一生来说,顺风顺水只是偶尔,挫折、不堪、焦虑和迷茫才是主旋律。我们登上并非我们所选择的舞台,演出并非我们所选择的剧本。继续加油吧!

目录

1.MySQL的多版本并发控制具体实现过程?怎么保证可重复读的?

2.redis的过期键清理策略?redis内存不足缓存的淘汰策略?

3.跳表的搜索过程?插入元素、删除元素后如何调整?

4.分布式事务?

5.TCP除了使用三次握手和四次挥手保证可靠性还有什么可以保证可靠性?

6.TCP四次挥手合并为3次行不行?

7.https加密过程?公钥、私钥、对称密钥怎样获取?

8.不可重复读和幻读的区别?

9.redis的string类型和zset类型的底层原理?

10.注册登录页面的测试要考虑哪些方面?

11.MySQL如何解决幻读?

12.MySQL分库分表问题?

13.为什么四次挥手等待2MSL?

14.进程调度说说吧?讲讲进程调度算法?

15.对称加密与非对称加密?

16.cookie与session?

17.说说了解的设计模式吧?

18.手写DCL,顺带说一下为什么这么写?

19.说说volataile和synchronized底层实现原理?

20.Object类下有哪些方法?

21.说说redis集群?

22.文件上传下载的文件大小怎么控制?

23.实际应用中前后端项目怎么部署?

24.如何保持数据库和redis的一致性?

25.说一下RabbitMQ的几种工作模式?

26.说一下netty吧,你知道NIO,BIO,AIO的区别吗?

27.线程之间通信有哪些方法?

28.说一下springcloud?

29.数学建模国赛做了什么?具体说一说细节?创新的东西?

30.说一下快速排序算法的细节?

31.个人博客印象最深刻的一篇?说说细节?


1.MySQL的多版本并发控制具体实现过程?怎么保证可重复读的?

参考链接:MVCC详解,深入浅出简单易懂-CSDN博客

mvcc,也就是多版本并发控制,是为了在读取数据时不加锁来提高读取效率和并发性的一种手段。mvcc所提到的读是快照读,也就是普通的select语句。快照读在读写时不用加锁,不过可能会读到历史数据。还有一种读取数据的方式是当前读,是一种悲观锁的操作。它会对当前读取的数据进行加锁,所以读到的数据都是最新的

在读已提交和可重复读隔离级别下的快照读,都是基于MVCC实现的!

 mvcc的实现,基于undolog版本链readview

在mysql存储的数据中,除了我们显式定义的字段,mysql会隐含的帮我们定义几个字段。

  • trx_id:事务id,每进行一次事务操作,就会自增1。

  • roll_pointer:回滚指针,用于找到上一个版本的数据,结合undolog进行回滚

首先看看readview:

我们用select读取数据时,这一时刻的数据会有很多个版本,但我们并不知道读取哪个版本,这时就靠readview来对我们进行读取版本的限制,通过readview我们才知道自己能够读取哪个版本

一个readview快照主要包含下面四个字段:

m_ids:活跃的事务就是指还没有commit的事务。

max_trx_id:例如m_ids中的事务id为(1,2,3),那么下一个应该分配的事务id就是4,max_trx_id就是4。

creator_trx_id:执行select读这个操作的事务的id。

 mvcc如何实现RC和RR的隔离级别:

(1)RC的隔离级别下,每个快照读都会生成并获取最新的readview。

(2)RR的隔离级别下,只有在同一个事务的第一个快照读才会创建readview,之后的每次快照读都使用的同一个readview,所以每次的查询结果都是一样的。

幻读问题:
快照读:通过mvcc,RR的隔离级别解决了幻读问题,因为每次使用的都是同一个readview。
当前读:通过next-key锁(行锁+gap锁),RR隔离级别并不能解决幻读问题。
 

2.redis的过期键清理策略?redis内存不足缓存的淘汰策略?

Redis使用惰性过期和定期过期两种策略来清理过期键。

  1. 惰性过期:当一个过期键被访问时,Redis会检查该键是否已过期,如果过期则删除它。这种策略的优点是能够节省CPU时间和内存,但缺点是可能会存在大量的过期键没有被及时清理。

  2. 定期过期:Redis默认每隔1秒钟检查一批随机键是否过期,如果发现有过期键则删除它们。这种策略的优点是能够保证过期键在一定时间内被清理,但缺点是可能会浪费一些CPU时间和内存,因为不是所有的过期键都需要在此时清理。

Redis内存不足的缓存淘汰策略提供了8种。

noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键。

allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键。

volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键。

allkeys-random:加入键的时候如果过限,从所有key随机删除。

volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐。

volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键。

volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键。

allkeys-lfu:从所有键中驱逐使用频率最少的键。

这八种大体上可以分为4中,lru、lfu、random、ttl。

lru:Least Recently Used),最近最少使用,优先淘汰最近未被使用的数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

lfu:Least Frequently Used,最不经常使用法(根据计数器,用的次数最少的key淘汰)。

ttl:Time To Live,生存时间,快要过期的先淘汰。

random:随机。

3.跳表的搜索过程?插入元素、删除元素后如何调整?

跳表,又叫做跳跃表、跳跃列表,在有序链表的基础上增加了“跳跃”的功能。
跳表在原来的有序链表上加上了多级索引,通过索引来快速查找;可以支持快速的删除、插入和查找操作。跳表实际上是一种增加了前向指针的链表,是一种随机化的数据结构。Redis中 的 SortedSet、LevelDB 中的 MemTable 都用到了跳表。对比平衡树, 跳表的实现和维护会更加简单, 跳表的搜索、删除、添加的平均时间复杂度是 O(logn)。

跳表 的性质:

跳表由很多层结构组成,level是通过一定的概率随机产生的;
每一层都是一个有序的链表,默认是升序 ;
最底层(Level 1)的链表包含所有元素;
如果一个元素出现在Level i 的链表中,则它在Level i 之下的链表也都会出现;
每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。
 

跳表的搜索过程:

  1. 从顶层链表的首元素开始,从左往右搜索,直至找到一个大于或等于目标的元素,或者到达当前层链表的尾部。
  2. 如果该元素等于目标元素,则表明该元素已被找到。
  3. 如果该元素大于目标元素或已到达链表的尾部,则退回到当前层的前一个元素,然后转入下一层进行搜索。

跳表元素的插入过程:

  • 当我们不断地往跳表中插入数据时,我们如果不更新索引,就有可能出现某2个索引节点之间的数据非常多的情况,在极端情况下,跳表还会退化成单链表。
  • 跳表是通过随机函数来维护“平衡性”。当我们在跳表中插入数据的时候,我们通过选择同时将这个数据插入到部分索引层中,如何选择索引层,可以通过一个随机函数来决定这个节点插入到哪几级索引中,比如随机生成了k,那么就将这个索引加入到,第一级到第k级索引中。

跳表的删除过程:

        在跳表中删除某个结点时,如果这个结点在索引中也出现了,我们除了要删除原始链表中的结点,还要删除索引中的。因为单链表中的删除操作需要拿到删除结点的前驱结点,然后再通过指针操作完成删除。所以在查找要删除的结点的时候,一定要获取前驱结点(双向链表除外)。因此跳表的删除操作时间复杂度即为O(logn)。
 

4.分布式事务?

参考博客:七种常见分布式事务详解(2PC、3PC、TCC、Saga、本地事务表、MQ事务消息、最大努力通知)_张维鹏的博客-CSDN博客

事务是保证单数据库的数据一致性,分布式事务是保证多数据库的数据一致性。

分布式事务:在分布式系统中一次操作需要由多个服务协同完成,这种由不同的服务之间通过网络协同完成的事务称为分布式事务,七种常见分布式事务(2PC、3PC、TCC、Saga、本地事务表、MQ事务消息、最大努力通知)

1) 2PC,两阶段提交,将事务的提交过程分为资源准备和资源提交两个阶段,并且由事务协调者来协调所有事务参与者,如果准备阶段所有事务参与者都预留资源成功,则进行第二阶段的资源提交,否则事务协调者回滚资源。

2)3PC,三阶段提交协议,是二阶段提交协议的改进版本,三阶段提交有两个改动点:

  • (1)在协调者和参与者中都引入超时机制
  • (2)在第一阶段和第二阶段中插入一个准备阶段,保证了在最后提交阶段之前各参与节点的状态是一致的。

3)TCC(Try Confirm Cancel)是应用层的两阶段提交,所以对代码的侵入性强,其核心思想是:针对每个操作,都要实现对应的确认和补偿操作,也就是业务逻辑的每个分支都需要实现 try、confirm、cancel 三个操作,第一阶段由业务代码编排来调用Try接口进行资源预留,当所有参与者的 Try 接口都成功了,事务协调者提交事务,并调用参与者的 confirm 接口真正提交业务操作,否则调用每个参与者的 cancel 接口回滚事务,并且由于 confirm 或者 cancel 有可能会重试,因此对应的部分需要支持幂等。

4)  Saga 事务核心思想是将长事务拆分为多个本地短事务并依次正常提交,如果所有短事务均执行成功,那么分布式事务提交;如果出现某个参与者执行本地事务失败,则由 Saga 事务协调器协调根据相反顺序调用补偿操作,回滚已提交的参与者,使分布式事务回到最初始的状态。Saga 事务基本协议如下:

(1)每个 Saga 事务由一系列幂等的有序子事务(sub-transaction) Ti 组成。
(2)每个 Ti 都有对应的幂等补偿动作 Ci,补偿动作用于撤销 Ti 造成的结果。
        与TCC事务补偿机制相比,TCC有一个预留(Try)动作,相当于先报存一个草稿,然后才提交;Saga事务没有预留动作,直接提交

5) 本地消息表的核心思路就是将分布式事务拆分成本地事务进行处理,在该方案中主要有两种角色:事务主动方和事务被动方。事务主动发起方需要额外新建事务消息表,并在本地事务中完成业务处理和记录事务消息,并轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。

        这样可以避免以下两种情况导致的数据不一致性: 业务处理成功、事务消息发送失败, 业务处理失败、事务消息发送成功。

6)  基于MQ的分布式事务方案本质上是对本地消息表的封装,整体流程与本地消息表一致,唯一不同的就是将本地消息表存在了MQ内部,而不是业务数据库中。

7)最大努力通知也称为定期校对,是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对的接口,如果事务被动方没有接收到主动方发送的消息,此时可以调用事务主动方提供的消息校对的接口主动获取。

5.TCP除了使用三次握手和四次挥手保证可靠性还有什么可以保证可靠性?

除了TCP的三次握手和四次挥手以外,TCP还采用以下一些机制来保证数据传输的可靠性:

  1. 数据校验(Checksum):每个TCP报文段都包含一个16位的校验和字段,用于检测数据在传输过程中是否出现错误或被篡改。

  2. 窗口控制(Window Control):TCP使用滑动窗口协议来进行流量控制和拥塞控制,确保发送方与接收方之间的数据传输速度匹配,避免了数据丢失和网络拥塞问题。

  3. 超时重传(Timeout and Retransmission):当数据包未收到确认或者丢失时,TCP会启动超时重传机制,重新发送未确认的数据包,直到得到对方的确认信息。

  4. 流量控制(Flow Control):TCP使用滑动窗口机制进行流量控制,确保发送方不会发送过多的数据导致接收方无法处理。在接收方的窗口大小为0时,发送方需要等待接收方的窗口变大后再发送数据。

  5. 拥塞控制(Congestion Control):慢启动、拥塞避免、快重传、快恢复,TCP使用拥塞窗口机制来进行拥塞控制,当网络出现拥塞时会降低发送方的发送速度,以避免拥塞加剧。

    慢启动(Slow Start)、拥塞避免(Congestion Avoidance)、快重传(Fast Retransmit)和快恢复(Fast Recovery)是TCP拥塞控制算法的四个组成部分。慢启动:在开始发送数据时,TCP会启动慢启动算法,每经过一个往返时间RTT(Round Trip Time),拥塞窗口cwnd就会加倍。这样可以快速占用网络带宽,加快数据传输速度。拥塞避免:当拥塞窗口cwnd达到一个阈值ssthresh时,TCP会停止慢启动,进入拥塞避免状态。此时每经过一个RTT,拥塞窗口cwnd只会增加1,缓慢提高数据传输速率,以避免网络出现拥塞。快重传:当发送方连续收到3个重复的ACK确认信息时,说明接收方已经丢失了某些数据包,TCP会立即重传这些数据包,而不需要等待超时重传。快恢复:当发送方进行快重传时,会将拥塞窗口cwnd减半,并将阈值ssthresh设置为当前拥塞窗口的一半。但是为了避免网络资源浪费,TCP会尝试快速恢复,即在恢复拥塞窗口cwnd后立即进入拥塞避免状态,继续发送数据。

通过以上机制的配合,TCP可以保证数据传输的可靠性,在网络环境发生变化时能够自适应调整,提高数据传输的效率和可靠性

6.TCP四次挥手合并为3次行不行?

在一些情况下, TCP 四次挥手是可以变成 TCP 三次挥手的。

当服务端在 TCP 挥手过程中,「没有数据要发送」并且「开启了 TCP 延迟确认机制」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。
 

7.https加密过程?公钥、私钥、对称密钥怎样获取?

HTTPS(也称为HTTP Secure)是一种通过使用SSL/TLS加密通信内容的安全版本的HTTP协议。

HTTPS加密过程如下:

  1. 客户端向服务端发起HTTPS请求,请求中包含一个随机数和支持的加密算法列表。

  2. 服务端回应客户端的请求,返回自己的证书和公钥,以及选择的加密算法。

  3. 客户端收到服务端的回应后,首先验证证书的合法性和有效性,然后从证书中获取服务端的公钥

  4. 客户端对使用相应的算法生成一个随机的对称加密密钥,并用服务端的公钥进行加密,然后将加密后的对称密钥发送给服务端。

  5. 服务端接收到客户端发送的加密后的对称密钥,利用私钥进行解密,得到对称密钥。

  6. 服务端使用对称密钥加密要发送给客户端的数据,并发送给客户端。

  7. 客户端接收到服务端发送的加密数据,利用对称密钥进行解密,得到明文数据。

客户端和服务端如何获取公钥和私钥以及对称密钥的方式如下:

  • 获取证书和公钥:服务端需要申请并获得数字证书,包括其公钥和身份信息。证书可以从证书颁发机构(CA)或其他可信源获取。客户端在收到服务端的证书后,从证书中提取公钥。

  • 获取私钥:服务端可以从证书中获取私钥自己生成并妥善保管私钥。私钥只有服务端拥有,用于解密客户端加密的对称密钥。

  • 获取对称密钥:客户端和服务端协商并生成一个随机数,作为对称密钥,客户端生成该对称密钥。并利用非对称加密算法(如RSA)进行加密和解密,实现对称密钥的安全传输。

在加密通信中,客户端需要生成密钥用于加密和解密数据。生成密钥的方法一般有以下几种:

  1. 随机数生成法:客户端可以利用伪随机数生成器(PRNG)生成一个随机数序列,并根据该序列生成一个对称密钥。该方法简单、快速,但安全性较低,易受到攻击。

  2. 密码学哈希函数法:客户端可以使用密码学哈希函数(如SHA-256)对一个随机数或者密码进行哈希运算,得到一个摘要值作为对称密钥。该方法具有较高的安全性和不可预测性,但计算复杂度较高。

  3. 密钥交换协议法:客户端可以通过密钥交换协议(如Diffie-Hellman协议)与服务端协商生成一个共享的对称密钥。该方法是计算量较大、安全性高的方法,广泛应用于网络加密通信中。

  4. 其他方法:客户端也可以采用其他方法生成密钥,如基于物理特性的随机数发生器(如热噪声、放射性衰变等),或者结合多个方法生成密钥,以提高安全性和不可预测性。

总之,密钥生成是加密通信中的重要环节,客户端需要选择合适的方法生成安全可靠的密钥。

8.不可重复读和幻读的区别?

不可重复读和幻读都是数据库事务并发控制中的问题,但是它们发生的原因不同。幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

不可重复读指的是在一个事务中多次读取同一行数据,但是每次读取得到的结果不同。这是由于其他并发事务在该事务两次读取之间修改了该行数据所致

而幻读则是在一个事务执行过程中,多次查询得到的结果集不同,即出现了"幻影"数据,这是由于其他并发事务在该事务两次查询之间插入了新的符合条件的行数据所致。

简而言之,不可重复读是由于更新操作导致,而幻读则是由于插入或删除操作导致。

9.redis的string类型和zset类型的底层原理?

Redis 的 string 类型和 zset 类型底层实现的数据结构不同。

String 类型的底层实现是简单动态字符串(Simple Dynamic String,SDS),它类似于 C 语言中的字符数组,但支持自动扩容,并且提供了一些字符串操作函数。在 Redis 中,string 类型的值可以是一个字符串,也可以是一个数字,支持多种操作,如设置、获取、追加、自增等。

Zset 类型则是基于跳表(Skip List)实现的有序集合,它是一种可以同时兼顾查找效率和插入效率的数据结构。在 Redis 中,zset 类型的值是有序的字符串/数字列表,每个元素都有一个分数(score),根据分数可以进行排序、范围查找、删除等操作。

总之,Redis 的 string 和 zset 类型底层实现的数据结构分别是 SDS 和跳表,各自针对不同的场景和操作提供了高效的存储和访问方式。

10.注册登录页面的测试要考虑哪些方面?

一般从功能、性能、安全性、可用性、兼容性、界面、本地化进行测试。

一、功能测试

1、输入正确的用户名和密码,点击提交按钮,验证是否能正确登录;

2、输入错误的用户名或者密码,验证登录会失败,并提示相应的错误信息;

3、登录成功后能否跳转到正确的页面;

4、检查能否选择不同的登录方式进行登录(如使用手机号、微信扫码等);

5、有验证码时,要考虑文字是否辨认难度大,考虑颜色、刷新或者换一个的按钮是否好用;

6、登录成功后,是否记住用户名、密码功能;

7、登录失败后,不能记住密码的功能;

8、输入密码时,大写键盘开启是否有提示信息;

9、密码是否非明文显示,是否使用星号或圆点等符号代替;

10、登录页面中的注册、忘记密码、退出用另一账号登录等链接是否正确;

11、什么都不输入、点击提交按钮,检查提示信息;

二、性能测试

1、打开登录页面,需要的时间是否在需求要求的时间内;

2、输入正确的用户名和密码后,检查登录成功跳转到新页面的时间是否在需求要求的时间内;

3、模拟大量用户同时登录,检查一定压力下能否正常登录跳转;

三、安全性测试

1、登录成功后生成的Cookie,是否是httponly(否则容易被脚本盗用);

2、用户名和密码是否通过加密的方式,发送给服务器;

3、用户名和密码的输入框,应该屏蔽SQL注入攻击;

4、用户名和密码的输入框,应该禁止输入脚本(防止XXS攻击);

5、防止暴力破解,检测是否有错误登录的此时限制;

6、是否支持多用户在同一台机器上登录;

7、同一用户能否在多台机器上登录;

四、可用性测试

1、是否可以全用键盘操作,是否有快捷键;

2、输入用户名、密码后按回车,是否可以登录;

五、兼容性测试

1、不同浏览器下能否显示正常且功能正常;

2、同种浏览器不同版本下能否显示正常且功能正常;

3、不同操作系统能否显示正常且功能正常;

4、不同分辨率下能否显示正常且功能正常;

六、界面测试

1、界面布局是否合理,textbox和按钮是否整齐;

2、textbox和按钮的长度、高低是否符合要求;

3、界面的设计风格是否与UI的设计风格统一;

4、图片,颜色,字体,超链接,是否都显示正确;

七、本地化测试

1、不同语言环境下,页面的显示是否正确;
 

11.MySQL如何解决幻读?

MySQL默认采用的隔离级别是可重复读,在这种隔离级别下不同的读模式,针对幻读问题采用了不同解决方案:

快照读在读写时不用加锁,不过可能会读到历史数据。

还有一种读取数据的方式是当前读,是一种悲观锁的操作。它会对当前读取的数据进行加锁,所以读到的数据都是最新的

针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读。
针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读。
但是,强调一点的是,MySQL在可重复读级别下,并没有完完全全的解决幻读问题,特别是在一个事务的快照读和当前读穿插使用的场景下,还是会出现幻读的情况。

12.MySQL分库分表问题?

首先看看为什么要进行分库分表:

以MySQL为例,单库数据量在5000万以内性能比较好,超过阈值后性能会随着数据量的增大而明显降低。单表的数据量超过1000w,性能也会下降严重。这就会导致查询一次所花的时间变长,并发操作达到一定量时可能会卡死,甚至把系统给拖垮。

​我们是否可以通过提升服务器硬件能力来提高数据处理能力?能,但是这种方案很贵,并且提高硬件是有上限的。那我们能不能把数据分散在不同的数据库中,使得单一数据库和表的数据量变小,从而达到提升数据库操作性能的目的? 可以,这就是数据库分库分表。

​分库分表就是把较大的数据库和数据表按照某种策略进行拆分。目的在于:降低每个库、每张表的数据量,减小数据库的负担,提高数据库的效率,缩短查询时间。另外,因为分库分表这种改造是可控的,底层还是基于RDBMS,因此整个数据库的运维体系以及相关基础设施都是可重用的。
 

分库分表的方式:

垂直分表:将一个表的字段分散到多个表中,每个表存储其中一部分字段。可以把一个宽表的字段按访问频次、业务耦合松紧、是否是大字段的原则拆分为多个表,这样既能使业务清晰,还能提升部分性能。拆分后,尽量从业务角度避免联查,否则性能方面将得不偿失。
垂直分库:按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,从而达到多个服务器共同分摊压力的效果。可以把多个表按业务耦合松紧归类,分别存放在不同的库,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能,同时能提高整体架构的业务清晰度,不同的业务库可根据自身情况定制优化方案。但是它需要解决跨库带来的所有复杂问题。
水平分库:把同一个表的数据按一定规则拆分到不同的数据库中,每个库可以放在不同的服务器上。可以把一个表的数据(按数据行)分到多个不同的库,每个库只有这个表的部分数据,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能。它不仅需要解决跨库带来的所有复杂问题,还要解决数据路由的问题。
水平分表:是在同一个数据库内,把同一个表的数据按一定规则拆分到多个表中。可以把一个表的数据(按数据行)分到多个同一个数据库的多张表中,每个表只有这个表的部分数据,这样做能小幅提升性能,它仅仅作为水平分库的一个补充优化。

13.为什么四次挥手等待2MSL?

MSL最长报文段寿命Maximum Segment Lifetime

两个理由:1)保证A发送的最后一个ACK报文段能够到达B。2)防止“已失效的连接请求报文段”出现在本连接中。

1、为了保证客户端发送的最后一个ACK报文段能够到达服务器。因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态。

2、他还可以防止已失效的报文段。客户端在发送最后一个ACK之后,再经过经过2MSL,就可以使本链接持续时间内所产生的所有报文段都从网络中消失。从保证在关闭连接后不会有还在网络中滞留的报文段去骚扰服务器。

注意:在服务器发送了FIN-ACK之后,会立即启动超时重传计时器。客户端在发送最后一个ACK之后会立即启动时间等待计时器

14.进程调度说说吧?讲讲进程调度算法?

进程调度算法也称 CPU 调度算法,当 CPU 空闲时,操作系统就从就绪队列中按照一定的算法选择某个就绪状态的进程,并给其分配 CPU。

(1)先来先服务调度算法(FCFS)

先来先服务调度算法是一个非抢占式的调度算法,顾名思义就是每次从就绪队列选择最先进入队列的进程,然后一直运行,直到进程退出或被阻塞,才会继续从队列中选择第一个进程接着运行.

(2)短作业优先调度算法

短作业优先(SJF)调度算法就是会优先选择运行时间最短的进程来运行,这有助于提高系统的吞吐量.

(3)最短剩余时间优先算法

最短剩余时间优先算法(SRTN)是最短作业优先的抢占式版本。

当一个新的进程到达时,把它所需要的整个运行时间与当前进程的剩余运行时间作比较。如果新的进程需要的时间更少,则挂起当前进程,运行新的进程,否则新的进程等待。

(4)高响应比优先调度算法
高响应比优先 (HRRN)调度算法主要是权衡了短作业和长作业,因为上面的先来先服务算法和短作业优先调度算法都没有很好的权衡短作业和长作业。那高响应比优先调度算法是怎么做的呢?

每次进行进程调度时,先计算响应比优先级,然后把响应比优先级最高的进程投入运行,响应比优先级的计算公式:
优 先 权 = ( 等 待 时 间 + 要 求 服 务 时 间 ) / 要 求 服 务 时 间 优先权 = (等待时间 + 要求服务时间) / 要求服务时间
优先权=(等待时间+要求服务时间)/要求服务时间
(5)时间片轮转调度算法

最古老、最简单、最公平且使用最广的算法就是时间片轮转(RR)调度算法.

(6)最高优先级调度算法

上面的时间片轮转调度算法中所有的进程的优先级都是一样的,但是,对于多用户计算机系统就有不同的看法了,它们希望调度是有优先级的,即希望调度程序能从就绪队列中选择最高优先级的进程进行运行,这称为最高优先级(HPF)调度算法。

(7)多级反馈队列调度算法

多级反馈队列(Multilevel Feedback Queue)调度算法是「时间片轮转算法」和「最高优先级算法」的综合和发展。

  • 「多级」表示有多个队列,每个队列优先级从高到低,同时优先级越高时间片越短。
  • 「反馈」表示如果有新的进程加入优先级高的队列时,立刻停止当前正在运行的进程,转而去运行优先级高的队列;

15.对称加密与非对称加密?

1、区别:加密一般分为两种,对称加密和非对称加密。对称加密就是加密解密都用同一个秘钥,比如DES、3DES(TripleDES)和AES等。
非对称加密就是加密和解密不是用的同一种秘钥,比如RSA算法、DSA算法、ECC算法、DH算法等。
在非对称加密中,用来加密的秘钥叫公钥,用来解密的秘钥叫私钥。公钥和私钥都是成对生成的,公钥分发给其他人用来加密,私钥用来解密。
2、优缺点:
对称加密:解密速度快,但保密性差。
非对称加密:加密算法保密性好,它消除了最终用户交换密钥的需要。但是加解密速度要远远低于对称加密。
 

16.cookie与session?

可以参考这个博客:cookie与session

(1)Session是服务器存储数据的一种机制,键值对结构的,主要用来存储身份相关的信息。session保存在服务器端(客户端仅保存一个sessionID),Cookie是客户端(浏览器)存储数据的一种机制,键值对结构,可以存储身份信息,也是可以存储关键的信息,都是程序员自定义Cookie保存在客户端。

(2)session保存的是对象,Cookie保存的是字符串。cookie的存储限制数据量不大于4KB,而session的容量则是无限量的。

(3)session不能区分路径,同一个用户在访问同一个Web应用程序期间,所有的session在任何路径都可访问。Cookie中如果设置了路径参数,则在其他路径下无法访问。

(4)session默认保存SeesionID的方法是Cookie,如果客户端禁用了Cookie,服务器端应采用其他的替代方法替代SessionID。

(5)session在会话结束后就会关闭,但是Cookie可以经过持久化而长期保存在客户端的本地硬盘上。所以session也更安全。

17.说说了解的设计模式吧?

单例模式,代理模式,工厂模式。

参照这篇博客:服务端开发Java面试复盘篇1_java服务端面试_nuist__NJUPT的博客-CSDN博客

18.手写DCL,顺带说一下为什么这么写?

单例模式中有饿汉模式和懒汉模式,懒汉模式可能存在线程安全问题,使用使用双重检查进行实例化,单例模式要程序运行期间,有且仅被实例化一次。第一次判断对象未初始化,进行加锁操作,加锁范围是单例类对象,同一时间只有一个线程可以拿到这个锁资源,保证实例化过程不会并行。

再讲讲为什么要加volatile关键字,对象实例化过程:

申请内存空间
根据对象类型初始化
内存地址赋值对象引用
但是JVM执行会有指令重排序,2、3步骤可能乱序执行,单机下不会发生问题,多线程情况,A线程发现未实例化,去申请锁实例化执行到1、3,线程B开始判断,此时uniqueInstance不为空,但是没有初始化,B线程拿到后可能会抛出空指针。
 

//使用双重检查锁的方式保重线程安全
class Singleton3{
    //使用双重检查进行初始化的实例必须使volatile关键字修饰
    private volatile static Singleton3 instance3 = null ;
    public Singleton3 getInstance3(){
        if(instance3 == null){
            synchronized (Singleton3.class){
                if(instance3 == null){
                    instance3 = new Singleton3();
                }
            }
        }
        return instance3 ;
    }
}

19.说说volataile和synchronized底层实现原理?

volatile是轻量级的synchronized,在多处理器开发中保证了共享变量的可见性。不会引起线程上下文切换和调度。volatile和synchronized都是Java中用于实现多线程同步的关键字,但是它们底层实现原理不同。

1. volatile的底层实现原理

volatile关键字主要作用是保证变量的可见性和禁止指令重排,即当一个线程修改了volatile修饰的变量时,其他线程能够立即看到最新的值。这是因为JVM在编译时会在读写volatile变量的代码前后插入内存屏障,对于写操作会强制刷新缓存并写回到主内存,对于读操作会从主内存中获取最新值,并将该值刷回到线程缓存中。

2. synchronized的底层实现原理

synchronized关键字作用是实现线程之间的协调与互斥,它由两部分组成:锁的获取和释放。在Java中,每个对象都有一个监视器锁(monitor),进入synchronized块时会尝试获取锁,如果锁已经被其他线程获取,则当前线程会阻塞直到锁被释放。在JVM底层,synchronized块的实现是通过monitorenter和monitorexit指令来实现的,其中monitorenter指令用于获取锁,monitorexit指令用于释放锁。当一个线程执行monitorenter指令时,如果该对象的锁未被其他线程获取,则该线程会将锁的持有者设置为本身,并且将计数器设置为1;若该对象的锁已被其他线程获取,则该线程会被阻塞,直到获得锁为止。同时,monitor中还记录了锁持有者和等待该锁的线程队列。当一个线程执行完synchronized块中的代码后,会执行monitorexit指令释放锁,将计数器减1,如果计数器为0,则该对象的锁被完释程同步的关键字,但是它们底层实现原理不同。volatile保证变量的可见性和禁止指令重排,是通过插入内存屏障实现的;而synchronized是实现线程之间的协调与互斥,底层是通过获取和释放锁实现的。

20.Object类下有哪些方法?

toString()方法

        toString()方法可以将任何一个对象转换成字符串返回,返回值的生成算法为:getClass().getName() + '@' + Integer.toHexString(hashCode())。不过,一般子类都会重写该方法。

equals方法

        Object类中的equals方法,用来比较两个引用的虚地址。当且仅当两个引用在物理上是同一个对象时,返回值为true,否则将返回false。不过,一般子类都会重写该方法。

hashCode方法

        获取对象的哈希码值,为16进制。

clone方法

        保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

getClass方法

        final方法,获取类的class对象。

wait方法

        导致线程进入等待状态,直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。wait(long timeout)可以设定一个超时间隔,调用该方法后当前线程进入睡眠状态,直到以下事件发生:

        (1)其他线程调用了该对象的notify方法。

        (2)其他线程调用了该对象的notifyAll方法。

        (3)其他线程调用了interrupt中断该线程。

        (4)时间间隔到了。

        此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

notify方法

        该方法唤醒在该对象上等待的某个线程。该方法只能在同步方法或同步块内部调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。

notifyAll方法

        解除所有那些在该对象上调用wait方法的线程的阻塞状态。该方法只能在同步方法或同步块内部调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
 

21.说说redis集群?

所谓的集群,就是通过增加服务器的数量,提供相同的服务,从而让服务器达到一个稳定、高效的状态。单个redis存在不稳定性。当redis服务宕机了,就没有可用的服务了。而且单个redis的读写能力是有限的。使用redis集群可以强化redis的读写能力,并且当一台服务器宕机了,其他服务器还能正常工作,不影响使用。

1.redis集群中,每一个redis称之为一个节点。
2.redis集群中,有两种类型的节点:主节点(master)、从节点(slave)。
3.redis集群,是基于redis主从复制实现。

主从复制模型中,有多个redis节点。 其中,有且仅有一个为主节点Master。从节点Slave可以有多个。只要网络连接正常,Master会一直将自己的数据更新同步给Slaves,保持主从同步。

主节点Master可读、可写。从节点Slave只读。(read-only)
因此,主从模型可以提高读的能力,在一定程度上缓解了写的能力。因为能写仍然只有Master节点一个,可以将读的操作全部移交到从节点上,变相提高了写能力。

当主节点宕机了,整个集群就没有可写的节点了。
由于从节点上备份了主节点的所有数据,那在主节点宕机的情况下,如果能够将从节点变成一个主节点,就可以解决这个问题了。这就是Sentinel哨兵的作用。

Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:
监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

在哨兵模式中,仍然只有一个Master节点。当并发写请求较大时,哨兵模式并不能缓解写压力。
只有主节点才具有写能力,那如果在一个集群中,能够配置多个主节点,就可以缓解写压力了,这就是redis-cluster集群模式。

1.由多个Redis服务器组成的分布式网络服务集群;
2.集群之中有多个Master主节点,每一个主节点都可读可写;
3.节点之间会互相通信,两两相连;
4.Redis集群无中心节点。

22.文件上传下载的文件大小怎么控制?

 在springboot中,在application.yml配置文件中设置文件存储路径(这里假设是本地的E:\ptms中)、以及限制上传文件大小(这里设置为40M)。

23.实际应用中前后端项目怎么部署?

前端部署到nginx,后端部署到tomcat。

参照博客:1. 前后端项目部署_前后端部署_我是小菜鸟,喜欢C和V的博客-CSDN博客

24.如何保持数据库和redis的一致性?

一般执行更新和删除操作,需要对缓存和数据库进行修改,保证缓存和数据库的一致性。一般主要考虑有下面四种策略:

先更新缓存,再更新数据库;先更新数据库,再更新缓存;先删除缓存,再更新数据库;先更新数据库,再删除缓存。

1.如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。

2.如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。

解决方案:

第一种方案:采用延时双删策略

设置缓存过期时间:删除缓存、写数据库、延时、再删除缓存。

从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

该方案的弊端

结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。
 

第二种方案:异步更新缓存(基于订阅binlog的同步机制)

读Redis:热数据基本都在Redis
写MySQL:增删改都是操作MySQL
更新Redis数据:MySQ的数据操作binlog,来更新到Redis
Redis更新

读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。

这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。

其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。
 

25.说一下RabbitMQ的几种工作模式?

Rabbitmq的工作模式有六种:simple简单模式、work工作模式、publish/subscribe订阅模式、routing路由模式、topic 主题模式、RPC模式。

simple简单模式为一个队列中一条消息,只能被一个消费者消费。

Work工作模式为一个生产者,多个消费者,每个消费者获取到的消息唯一。

publish/subscribe订阅模式为一个生产者发送的消息被多个消费者获取。

routing路由模式为生产者发送的消息主要根据定义的路由规则决定往哪个队列发送。

topic 主题模式为生产者,一个交换机(topicExchange),模糊匹配路由规则,多个队列,多个消费者。

RPC模式为客户端 Client 先发送消息到消息队列,远程服务端 Server 获取消息,然后再写入另一个消息队列,向原始客户端 Client 响应消息处理结果。

26.说一下netty吧,你知道NIO,BIO,AIO的区别吗?

Netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。

BIO g (Blocking  I/O):同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待
其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可
以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问
题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面
对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效
的 I/O 处理模型来应对更高的并发量。

 NIO  (New  I/O): NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。NIO提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应 de 的 SocketChannel 和ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。
AIO  (Asynchronous  I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
 

27.线程之间通信有哪些方法?

多个线程在并发执行的时候,他们在CPU中是随机切换执行的,这个时候我们想多个线程一起来完成一件任务,这个时候我们就需要线程之间的通信了,多个线程一起来完成一个任务,线程通信一般有3种方式:

通过 volatile 关键字:通过 volatile 关键字来实现这个任务,这个也是最简单的一种实现方式,大致思路 volatile 是共享内存的,两个线程共享一个标志位,当标志位更改的时候就执行不同的线程。
通过 Object类的 wait/notify 方法:我们的线程执行了很多次空循环,来等待另外一个线程来获取锁,这种操作无疑是十分消耗CPU的资源的,所以说为了解决这种情况,我们就需要一种机制可以实现线程之间的通信,可以唤醒其他的线程,而不是等待直到自己获取CPU的时间片,我们都知道,Object类提供了三个线程间通信的方法,wait(),notify(),notifyAll()。这三个方法必须都在同步代码块中执行的。
通过 condition 的 await/signal 方法:await操作会立刻释放掉锁,进入阻塞状态,singal会唤醒等待队列中的头节点(失败就依次唤醒)。
 

28.说一下springcloud?

包括Eureka、Hystrix、Ribbon、Feign、Zuul等核心组件。

1、 Eureka:服务治理组件,包括服务端的注册中心和客户端的服务发现机制;

2、 Ribbon:负载均衡的服务调用组件,具有多种负载均衡调用策略;

3、 Hystrix:服务容错组件,实现了断路器模式,为依赖服务的出错和延迟提供了容错能力;

4、 Feign:基于Ribbon和Hystrix的声明式服务调用组件;

5、 Zuul:API网关组件,对请求提供路由及过滤功能
 

29.数学建模国赛做了什么?具体说一说细节?创新的东西?

30.说一下快速排序算法的细节?

快速排序(Quicksort)是一种常用的排序算法,它的实现思想可以概括为以下几个步骤:

  1. 选择一个基准元素(pivot),通常选择待排序序列的第一个或最后一个元素。
  2. 将序列分为两个部分,左边的部分都小于等于基准元素,右边的部分都大于等于基准元素。这个过程叫做划分(partition)。
  3. 对左、右两个部分递归地进行快速排序。
  4. 通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
  5. 快排是分治的思想,基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。

31.个人博客印象最深刻的一篇?说说细节?

猜你喜欢

转载自blog.csdn.net/nuist_NJUPT/article/details/129940483