前言
面试官很有亲和力,而且底层基础知识非常厉害,个人对底层知识了解比较少,最近也是一直在补习.问到不是很会的问题面试官一直在通过我所了解的知识引导我回答.
总的感觉下来,面试官更倾向于了解我实际项目工作使用技术有无去了解它的工作原理以及对基础内容知识的关心程度,虽然没问八股文.但是还是比较有深度的问题(只不过我了解的还是比较浅显,回答有待提高)下面是我印象比较深刻的面试题.
个人最近项目介绍
介绍个人工作内容,负责职责模块.以及公司技术栈以及公司业务场景
Linux的ZSet底层数据结构(有score可以进行排序)
这里我说的是HashMap.但是HashMap适合等值查找快速,并且KV结构.但是并不支持排序与快速的范围查找.引导我想让我查找有序我又想到了二叉树,二叉查找树,还是没对(但是思想是这样的,先查找元素范围,再在范围内查询,减少查找次数).
- 一个redis对象由type(数据类型),encoding(对象编码方式),lru(过期时间),refcount(引用次数),*ptr(对象指针,指向对象数据)
- 每种数据类型有至少两种编码方式,ZSet编码方式为zipList(压缩列表)/skiplist(跳表).
- 当元素数量小于128且元素长度均小于64字节会使用zipList.否则使用skiplist.可通过zset-max-ziplist-entries和zset-max-ziplist-value来修改
- ziplist为顺序的压缩列表,以member,scope,member,scope格式由小到大存储
- skiplist编码为一个zet结构体,包含一个字典和跳跃表,跳跃表保存score从小到大顺序的集合元素,字典保存member到score的映射,保证等值查询score的效率.
- 跳跃表本身也是链表的一种(跳表链路层级最大不超过32),如下图,跳表在链表基础上增加链表层级概念,并且每层中数据个数随机的,每个数据除下有第一层链接外,高层链接按照一定规律计算.
- 跳表查询时会像二叉查找树一样,先比对最大链表大小,大就往下一个链表层级找,小就往该链表下一个节点找.类似左右子树概念.但省去了平衡的操作(每个节点的链表层级一定是连续的)
跳表和二叉查找树还是有一点点共性的,就是他们的查找机制,不过二叉树本身有序同时也对数据进行了分类操作(子树),跳表则是对数据进行了分区,先找到数据的分区头(即查找的值小于分区最大值),再在该分区内部找该对象,减少查找次数
MySQL的Innodb和MyISAM引擎区别
- 最主要区别是锁粒度、事务以及索引
- Innodb: 支持事务、锁粒度最小为行级记录、有聚簇索引(必须有主键,文件放在索引叶子节点上),支持唯一索引,5.7后支持全文索引、不记录表行数,需通过count语句查询
- MyISAM: 不支持事务、锁粒度最小为表级、索引均为非聚簇索引(索引,文件分离),支持全文索引、不支持外键、记录表行数,可直接读取
B树和B+树数据结构(多叉平衡查找树)
这里之前看过,但是对多叉平衡查找树或者说2-3/2-3-4树可能了解还是不够,没有理解都忘掉了,需要补习
- B+树中仅叶子节点会存储数据引用信息,并且是顺序排列,叶子节点存储相邻节点指针,便于范围查询/扫描.而且降低节点占用内存,方便单节点存储更多元素,降低树高度
- B树的所有节点中都有数据引用信息,读取多个信息需要进行随机IO比较慢.
Websocket协议基于那个协议
- Websocket是一个应用层的全双工通信协议,基于传输层的TCP协议保证可靠传输,也就是说Websocket也需要三次握手与四次挥手进行连接的建立与断开.
- Websocket协议规定的客户端与服务端第一次连接是通过http请求进行的,http请求在请求头上规定本次连接要进行升级操作升级为Websocket连接来进行通信,升级后后续的数据通信流程将按照Websocket协议进行.
- http连接header中有upgrade:websocket;connection:upgrade以及wensocket的版本信息、连接key密钥信息
- 更多信息可以看这里: WebSocket协议:5分钟从入门到精通
问了下Rabbitmq在Websocket实时通信模块中的作用
- 这里解释的是在Spring官方文档看的spring-boot-starter-websocket中集成外部消息代理器的作用,可能翻译的有点偏颇,个人觉得介绍不够完美需要再补充下知识
- Rabbitmq主要作为消息的代理中继器来替代SpringWbsocket的消息代理功能.消息发送过来时,Spring将消息通过与代理的连接传输到代理上,通过代理发送到连接到代理并订阅该路由的客户端
- 这里补充讲了Spring指定的与Websocket协议配合使用的Stomp文本传输协议.毕竟Websocket协议是一个基于TCP传输的应用层通讯协议,传输内容格式协议并未自己定义
- 也可以看下官方文档,注意下翻译问题,这里一直还是不是很能明白客户端最后连接在服务上还是代理上.或者连在服务上又去代理上创建的路由订阅
有没有测试实时通信服务最大连接数量
其实这里没有考虑过,但是如果大项目中这里面试官提醒到这点蛮关键的,如果一个节点不够支撑最大连接,开启多个节点的情况下: 如何保证接收到的信息能够发送到每一个连接到节点的用户上(统一的消息中继器).
Rabbitmq和Kafka在项目中的不同使用
- Rabbitmq主要作为消息解耦与实时通信使用,因为本身延时比较低,并且有消费确认机制保证消息可靠性
- Kafka基于批量发送与消费的特性,适合大数据高吞吐量消费应用场景,不用考虑数据的强完整性,比较适合日常普通日志信息传输使用
消息一致性保证以及消费幂等如何做的
- 消费者端开启ACK机制,先执行业务逻辑再手动ACK消费
- 接收到消息进行消息落盘,执行完业务逻辑,修改落盘消费状态再ACK
- 重复消息来临时,根据消息的唯一ID进行判别是否消费过
- 将该消息消费前存入Redis中,当消费完毕修改状态并添加过期时间.当重复消息在时间内重复发送将不会重复消费
项目日志收集如何做的
我们项目主要收集错误日志信息(这里本来拟对错误信息通过消息队列,在测试以及线上环境对我们开发/维护人员进行邮件发送),通过Spring-AOP的异常通知机制,将错误日志信息通过HTTP请求(feign)发送到我们的日志收集模块(我们日志收集比较简单,没有使用ELK构建日志收集查找功能),日志收集模块对日志落盘
进程和线程的区别
- 进程是系统进行资源分配和调度的一个独立单位.可以理解为一个运行的程序,一般情况下一个程序就代表一个进程
- 线程是进程的一个实体,是CPU调度和分派的基本单位.一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
进程切换系统会做什么
- 不会.操作系统级别知识都忘掉了
- 进程/线程上下文切换会用掉你多少CPU?
- 浅谈linux线程模型和线程切换
Docker如何实现的容器间隔离
- 进程隔离和资源隔离
- 进程隔离使用到了Linux内核Namespace技术,重新编写进程编号以及屏蔽父进层信息
- 资源隔离使用overlay其实还是使用的挂载技术,将在宿主机上创建的资源挂载到容器内
项目有没有分布式链路跟踪
我们项目上使用的是服务集成ZipKin做链路跟踪操作.面试官想问的是SkyWalking,可是只是看过简介还早都忘了
Seata在项目中的使用,二阶段提交流程(真提交/假提交)
使用的Seata的AT模式,一阶段进行事务数据提交并记录回滚日志(undo_log表),二阶段异步提交或者通过回滚日志反向补偿
所以一阶段已经提交了事务,二阶段回滚也是将执行的操作反向修改而已项目测试如何做的
我们这边只是写完接口通过PostMan进行接口测试,并没有使用过Mock框架.只是由测试组进行集成测试,压测测试使用的是Jmeter工具进行压测和自动化测试脚本.也可以用nGrinder做,但是我也只是了解过并没有直接使用过,所以这里忘记了.
个人技术路线规划
算法题: 给定一个M*N的二维矩阵,内容是0和1,0是湖,1是陆地.查找连着的最大的陆地面积(只能水平/垂直算连接)
动态规划(这里想的有偏僻,其实应该是递归回溯)+辅助标记(这里面试官提示深度优先遍历+广度优先遍历+辅助标记查找表)// 先每行遍历,当节点为1进入计算面积方法. // 计算面积方法计算完面积则将该地方置为0 // 从当前点向上下左右进行面积计算 public static Integer maxArea(int[][] weak){ int maxArea = 0; if(weak.length < 1){ return maxArea; } // 纵向遍历 for(int i=0; i < weak.length; i++){ // 横向遍历 for(int j=0; j < weak[i].length; j++){ // 当该节点出现岛屿时,执行计算面积方法.计算面积方法向该点的四面八方搜索 if(weak[i][j] == 1){ maxArea = Math.max(maxArea, search(weak, i, j)); } } } return maxArea; }
/**
*
- @param weak 湖
- @param i 纵坐标
- @param j 横坐标
- @return
- /
private static Integer search(int[][] weak, int i, int j){
if(weak[i][j] ==0){return 0;
}
int currentArea = 1;
// 将当前节点置为0,毕竟已经搜索过了算在这个岛的面积上了.不存在两个岛用同一个节点的情况,因为这种情况这两个岛就是相连的了.
weak[i][j] = 0;
// 向上
if(i-1 >= 0){currentArea = currentArea + search(weak, i-1, j);
}
// 向下
if(i+1 < weak.length){currentArea = currentArea + search(weak, i+1, j);
}
// 向前
if(j-1 >= 0){currentArea = currentArea + search(weak, i, j-1);
}
// 向后
if(j+1 < weak[i].length){currentArea = currentArea + search(weak, i, j+1);
}
return currentArea;
}
参考
redis zset内部实现
拼多多面试官问我zset底层是如何实现的,我反手就把跳表的数据结构画了出来
SpringBoot 实现 Websocket 通信详解
Docker是如何实现隔离的
Seata AT 模式
全部评论
(6) 回帖