面经
从网上看的视频
目录:
- 视频1:高级开发面试实录,用力过猛?4年经验竟被问到崩溃?
- 视频2:【迈向AL】百万年薪Java岗一面实录。河北王校长冲击百万-技术专家一面实录
- 视频3:前端小伙自学转Go开发面试中小厂录屏邀请各位来评分
- 视频4:【模拟Go面试】模拟3年20K-30KGo岗位基础面
视频1
高级开发面试实录,用力过猛?4年经验竟被问到崩溃?
你的技术栈是什么
- java mysql。。。
项目的QPS峰值是多少,响应时间
接口800ms慢在哪里。
- 校验,查询多。
- 优化方式:后面的操作用rabbitmq
redis
zset怎么做排行榜功能的,zset低层怎么实现的,优点
- 跳跃表,压缩队列
redis还有其他数据结构吗。了解 bitmap 吗
- string,hash
设计:已读未读消息如何用redis实现
- bitmap 布隆过滤器(回答错误)
除了性能优化,还有其他优化吗?怎么发现这个问题?
- 内存泄露GC。
mysql
SQL很慢的化,原因一般是哪些?
- 没有用到索引或者没有命中索引。
- 系统内存比较小。
- 事务死锁。
- 用 explain 看,type 是index还是什么,联合索引看length。
如果一张表有 a b c 三个字段的索引,通过 where b=x 能命中吗?
- 不能。不符合最左原则。
加了b的条件,也加了a的条件,可以吗?
- 也不行,必须是a在前面。(回答错误)
了解mysql的执行引擎吗?它的执行过程
- 了解Innode。解析,执行,优化。(问的不够清楚)
mysql会对SQL优化哪些点呢?
- 不知道
数据库死锁怎么排查?
- 有个指令看事务状态。再查看死锁可能的原因。
- 间隙锁的可能性比较大
间隙锁主要解决什么问题
- 解决可重复读情况下的幻读
多版本控制了解过吗
- mvcc。用于解决并发下面的冲突(回答不太好)
Innode有快照读和当前读有了解过吗(实际还想问mvcc原理)
- 共享锁 排他锁(回答错误)
介绍一下行锁和表锁有哪些?
- 行锁:记录锁
- 表锁:
知道意向锁吗?
- 不知道
一个事务正在更新表的数据,另外一个事务也在更新。它怎么判断有没有冲突?
- 可重复读做不了这个事情(回答不对)
spring
简单介绍一下spring的生命周期,启动容器bean的生命周期
- bean实例化,bean注入
容器启动,初始化,修改bean属性的默认值怎么去做
- 跟配置相关。我们用的 nacos ,可以实现。
分布式
nacos和zookeeper 哪个更适合做配置中心?
- 没用过 zookeeper
分布式系统下有CAP理论,你之道吧
- AP 和 CP。可用性和分区容错性。一致性和分区容错性。
- nacos 是都支持的。eureka 主要支持 AP。
nacos保证CAP时,在什么情况下是不可用的?
- 服务列表超过半数不可用。
系统有做过一些服务降级之类的东西吗?
- 用 springCloud hystrix。断路器,架构组有网关。
客户端是直接报错吗?
- 熔断的化会报错。(回答错误)
有办法让上游返回一个默认数据吗,不报错?
- 可以。看业务需求。(回答的不具体)
java
传输类实现了序列化接口。为什么要做序列化呢?
- 不清楚。
jdk里有泛型,为什么要做泛型呢?jdk 1.5之前没有泛型。解决了什么问题。
- 不知道
java的反射了解吗?有几种反射方式
- 三种。建立对象。通过路径。通过class类。(类.class;对象.getClass;forName)
类的加载的初始化阶段,了解过吗?
- 加载,链接,初始化。
问你一下解析阶段的顺序是固定的吗?一定是在初始化之前吗?
- 是 (回答错误)
java的晚绑定和早绑定了解吗?
- 不了解。
gc的问题除了内存泄露,还会有其他问题吗?
- 会有 stop the world状态。(具体回答的不好)
jdk 1.8 之后有个 G1 的收集器,它有什么优化吗?
- 它可以固定收集的时间。优先回收性价比高的辣垃圾。可预测的时间。
- 用的分代收集。
G1除了老年代,新生代,还有其他分区或者优化吗?
- 不清楚。
它低层有一个类似于卡表的技术,你了解吗
- 不了解
系统有用到多线程方面吗?java里的锁可以简单介绍吗?
- synchronized ,CAS , reentranlock 还有 AQS
volatile 有了解吗
- 解决一个内存可见性的问题,java的一个线程模型。巴拉巴拉。。。
volatile 变量定义了很多,会对性能有影响吗?
- 会。
总线风暴了解过吗?工作线程频繁嗅探其他线程的变量,消耗大量cpu带宽
- 不清楚。
fork join 了解过吗?解决什么问题。
- 不了解。
设计
促销,有上百w个用户。活动开始前5分钟通知100w用户,系统架构怎么设计。
- 将100w用户列表,放到线程池发短信。
注意:消息可能提前1天就创建好了,只是提前5分钟发送。这中间的流程怎么实现。可以用延迟消息之类的,怎么接收。
- 想不出来 (这里问的重点应该是100w用户,不可能开100w个线程。怎么解决。)
设计站内消息,单方面推送。设计已读,未读功能。可以用位图。
一条消息建一个bitmap还是一个用户建立一个bitmap
- 一条消息。
视频2
【迈向AL】百万年薪Java岗一面实录。河北王校长冲击百万-技术专家一面实录
自我介绍
- 8年半工作经验,就职于xxx。一直在做java编程。并发编程,bean结构,垃圾调优。mysql存储比较精通。
- 缓存方面对redis,memcache,ecache等都有经验。
- 我们这边的中间件是xxx,是基于kafka的再次的封装和使用方式的改进。
- 对 rabbitmq,kafka也是非常精通。对消息处理,重试机制都有经验。
- 缓存重点使用redis,对它有过深度的学习。
- spring boot,spring cloud 部署方案非常熟悉,高并发, 高可用非常的熟悉。
- 其他的 mongodb,zk 都很熟悉。分布式锁,分布式事务都所有精通。
- 目前是java高级开发工程师,主要是参与架构的设计和需求的分析。50%精力放在这里。
- 另外50%在需求代码的书写。 包括bug维护和上线等。
java基础
hashmap低层数据结构,hash寻址的原理。
- 低层是数组。在数组上做链表扩展。从1.8以后,数组位上除了放链表意外,还会将链表转换成红黑树。
- 寻址从put方法来说,这是一个关键。table为空,它会扩容,这是第一次,默认容量16。
- 不为空,就计算数组下标,使用hashcode以及table length - 1 进行与操作。key存在,hashmap里面会直接铺开。
- key不存在,对1.7来说直接进行判断。1.8会进行一个判断,树还是链表结构。如果是链表结构,会判断是否会转换成红黑树。
- 对添加来说,涉及hashmap扩容,需要考虑负载因子,默认0.75阈值。
- hashmap put 有4个参数。第一个是hash值,是通过hash code异或自身右移16位得到的值。值和table size -1进行与操作,定位数组里的槽位。
- 这里涉及一个扰动的处理。hash code的计算方式,hash code异或自身右移16位得到。
- 剩下是内存空间的开辟。之前已经说过了。
为什么要高16位和低16位异或操作呢?
- 任何类型调用的都是是hashcode方法,存在性能瓶颈。链表+红黑树。数据量足够大时,hash表的效率会降低。
- 为了寻址离散性,将32位的hashcode,高16位和低16位进行异或,得到一个更加随机的值。
为什么不用与,非。而是使用异或操作呢?
- 11 10 01 00 在and情况下,25%为1,其他情况都是0。
- 或操作,75%是1,25是0
- 这是and和or的概率分布。
- 异或 50% 为 1,50% 为0,保证均衡
java 1.8下hashmap什么情况下会转换成红黑树
- 一个桶位的链表长度大于8时,会尝试转换。过程中,会判断hashmap size。size的阈值是64.
- hashmap size 超过64,链表长度大于8 才会转换成红黑树。
- 如果链表长度小于8,size大于64,会考虑扩容。从64扩容成为128。槽位会重新计算。
concurrent hashmap jdk 1.7和1.8的区别
- 1.7之前对hashmap的一个安全性操作。还有人使用hash table。hashtable是锁全表的,会阻塞,性能不好。
- concurrent hashmap是对hashmap的一个嵌套。最大的并发量是16。此时有16个segment,分别被访问。
- 它使用 ReentraintLock 加锁。对每个segment进行加锁。
- 1.8进一步优化。16个segment使用 synchronized 加锁。其实是锁定桶位上的链表或者红黑树。
- 假设存储了20个值,理想情况下20个线程在操作各个值,也不会阻塞。1.8还使用了CAS操作。
- CAS消耗更多cpu,但是时间复杂度是O(logn)。但是效率是远高于1.7
- 所以 concurrent hashmap是目前并发的map里效率最高的。
mysql
系统的讲讲mysql整体的调优
首先,mysql对单行数据量的要求,一页是16kb。一条数据是1kb,一个内存页可以存储16条。
因为每次都是读取内存页的数据。所以要保证单行的值尽可能的小。
在初期需求分析的时候就要考虑。比如要做一个uuid。如果用32位的uuid,就要定死为32位。千万不要浪费mysql的空间。
因为存储空间大了之后,影响整个b+树的高矮胖瘦。因为b+树是高扇出性。所有的节点都是16kb的内存页。
根节点只有一个16kb的内存页,存储指针,指向下一页。1170个指针。所以第二层可以有1170个16kb的内存页。
第三层是 1170 * 1170个内存页。再乘以16,一共可以存放2000万个数据。
但是如果一条数据16kb,那么2000万除以16,存不了多少数据。
枚举尽量使用 tinyint,用 0 1 2 标记。一是控制数据量,增加b+树能够容纳数据的最高量。
这是初期第一步设计。表设计完了之后,对表进行增删改查。需要考虑索引。需要根据需求。
聚簇索引,辅助索引,覆盖索引。
聚簇索引每张表只有一个,主键。
辅助索引是我们自己加的,每个都是一个树。
覆盖索引是没有书的,一会介绍。
在sql设计的时候,不要使用 select *。因为select * 必须要走聚集索引,它要拿到所有的数据。只有在聚集索引里才有这些数据。
如果一定要使用 select * ,建议使用mysql 5.6 版本。因为它有一个离散读的优化,icp的优化。会快一些。
着重说下覆盖索引。假设n个索引都可以作为索引,就可以这样设计。因为我们可以在辅助索引里面可以找到这些数据。因为它存储的着主键和索引的值。
这样就可以避免回标的查询。
我们还需要使用执行计划,查看索引是否失效。这是一个细节。
第三个阶段。mysql数据量的增长。最主要的是一个partition分区。
有的用日期,有的用id。他们有局限性。单台mysql只支持1024个分区 ,一旦达到上限。
下一步就需要垂直拆分,水平拆分。就是单表变多表。
垂直拆分:
数据量越小,b+树容纳的数量越多。查询就会更好。
但是它也是有缺点的,在划分时,需要考虑关联性。一旦需要表关联的化,需要多考虑测试。
最好的场景是,表拆分开来之后,两个表还能分别支持各自的业务。
水平拆分:
是最复杂的。如果partition,垂直拆分还是不能满足的时候,就需要水平拆分。
比如订单,一天有两千万个。需要存放到两个不同的mysql服务器里,就需要一个算法。
把不同的订单分到不同的服务器。这时候日期就不行了。
有时候一个查询需要多个订单。这时候需要多个sql查询。所以我们需要考虑划分的键
还有一个方面,冷热备份
根据业务需求,将数据实时备份,使存储量达到一个稳定的状态。
真的是做过mysql的工作,是吧?
- 是的。从表的设计,到业务上表的查询和优化,以及备份。
java
java文件编译成class文件,文件的结构?
- 开头是一个魔数。每个class文件的魔数是一样的,标注它的类型。
- 然后是次版本号和主版本号。版本号为了定位使用jdk的版本。jdk是向下兼容的。
- 常量值计数器。后面就是常量池。计数器是为了统计里面有多少常量。
- 常量池里面有两大类。字面量和符号引用。字面量接近于常量。符号引用比如类,接口名,方法,动态调用点,package等。
- 后面是访问标志。default,public。访问属性。
- 后面是类索引,它的类有没有父类。
- 接口索引。在接口索引集合的后面。
- 字段表计数器。
- 字段表
- 方法表计数器
- 方法表。里面还有一个属性表。
- 类属性表计数器。
- 类属性表。
- 我研究过class的二进制文件,所以比较熟悉。
静态变量时使用 final static,编译之后存放在哪里呢。
- 会存放到字段表里面。附带的属性表里面的const属性里面。存放的是string值。
- 好处是,我们使用类.字段名调用时不需要初始化类。对动态加载类这块有性能提升。
常量池计数器是从0开始计算还是从1开始计算的。
从1开始计算的。里面存放了一个字面量和符号引用。要考虑到匿名的内部类,没有方法名,父类没有实现接口等。
这种情况下,如何进行一个指向。
比如object类没有父类,它的父类索引还是要指向。所以这种情况会给它常量值计数器第0个位置。
jvm的类的完整的加载流程,越详细越好
链接。。。记不清了。老的一套不熟。
第一步转换成class字节流
第二步,验证文件格式
第三步,加载class
第四步,将字节码转换成运行时方法区的结构
第五步,方法区类的数据入口
验证
最后进行一个准备
想起来了
1 加载
2 链接
- 验证,准备和解析
3 初始化
jvm 垃圾回收的流程
先从可达性分析来说。对不可达的数据进行垃圾处理。
从gc root遍历,涉及三色标记算法。
标记完成之后,将需要回收的对象进行回收。
gc root分为
- 方法区,一个栈。静态属性引用对象。常量引用对象。本地方法栈。
- 一个jvm内部自己的引用。
- 还有一个锁。锁对象涉及持有和释放。
三色标记原理
- 白色,黑色和灰色。白,还没被访问。黑,被访问了,以及被扫描完了。灰色,被访问了,但是没扫描完。
- 白色被列入回收名单。但是并不代表立刻回收。在回收过程中,会有一个f-q队列,finalize 。会被放入队列。这个队列有一个低优先级的线程处理。
在工作中有遇到过因为 finalize 逃逸导致的内存溢出吗。
- 工作中没有遇到过,因为我们不会复写这个方法。
简单描述jvm调优的心得
这个比较复杂,有几方面的经验。
在大访问压力下,minor gc比较频繁。如果时间不是过长,不引发full gc。那么我们可以适当增大eden区的大小。
这样会降低垃圾回收的频繁程度。当然,我们要同时保证垃圾清理引发的停顿时间能接受。
minor gc频繁,容易引发full gc。首先看minor gc的对象的大小能不能移动到 Survivor 区。
。。。
这是一个minor区和两个survivor区比例不均匀的问题,默认是8:1:1 。如果这批数据进入老年代,很快就进入full gc。
如果避免这个情况,在测试环境里压测,达到生产环境的量。通过 visualVM 看对象的大小,比例,整体的空间。
这样能保证minor gc 不会引发full gc。
第二点,是大对象的创建。gvm有个参数,p开头的hold。超过这个值,这个对象会直接进入老年代。会引发full gc。
首先看自己创建的对象是不是特别大。如果特别大,对象进行拆分。从代码层面。
如果对象不是太大,可以调整参数大小。
minor gc 和 full gc过程会引起停顿。
- 垃圾回收的真实时间就是很长。比如内存太大了,导致垃圾特别多。
- 垃圾回收时间不长,停顿是由于非垃圾回收导致的。需要等所有线程达到一个jvm的安全点。安全点可以通过参数去查看。看哪些线程没有按时到达安全点。
- 内存泄漏导致的。很可怕。
- 其他情况,代码写的有问题。循环体new对象等。
我使用 visualVM 进行jvm调优。线上有问题不会导致宕机,一个节点有问题,会dump出数据,另一个节点会进行扩容。
然后通过dump出的数据,导入到 visualVM,导致reference指向的堆栈信息,然后找到问题。
谈谈对 volatile CAS AQS synchronized方法以及lock的理解和认识
volatile是为了保证内存的可见性,对代码一个重排序。通过主线做的。主线上有缓存一致性协议,数据修改都会通过cpu传到主线,推送给其他cpu。标记不可用。下次读取就会从主存读取。
它还会禁止指令重排序。
CAS 比喻成轻量级锁,不block线程的情况下,实现同步的线程安全的数据读写。他们采用的一个for循环。
它也存在一个ABA问题。目前来说,可以用过版本号,时间戳,累加次数都可以解决。
AQS里面使用了大量的CAS操作。就是从第三方包里面以及避免了ABA问题。
CAS的好处是减少线程的block状态,就能减少内核态和用户态的转换,节省我们的时间。
但是CAS使用不当,就会导致CPU的过度消耗。
CAS目前还是建议处理返回比较快的业务场景,这样能减少循环。
AQS可以和lock一起说。常用的lock是retreenlock 和readwritelock。他们低层就是使用了AQS。
他们用自己的代码复制了一套类似synchronized同步加锁机制。
lock就是以AQS为低层,加上 lock condition,模仿了object的wait notify 类,也是做了加锁。
lock比synchronized更加灵活。是显式加锁,可以做超时,重试机制。
如果都用 retreenlock ,可以避免死锁的形成。有意识的使用 nanos的加锁方式,设置超时时间,去保证我们的程序不会死锁。
AQS的源代码看过吗,它里面用的什么设计模式?
用的模板方法模式。AQS里面有公平锁和非公平锁。它在加锁的时候,是可以让用户自己去实现的。
模板方法的好处在于,可扩展的方法让用户自己去实现。对于固有的,不需要改变,用AQS自己的代码去做。
如果对自己的锁有设计需求,可以使用AQS。
volatile 和 jvm 的内存屏障的关系
volatile禁止指令重排序使用了内存屏障。
jvm会在每个volatile的写前面加一个 StoreStore 屏障。也就是说写写这个操作不能重新排序。
jvm在每个写后面会插入一个 StoreLoad 屏障。
如果先写再读,在读后面会插入一个 LoadLoad 屏障。读也不能重排序。
读的后面还要插入一个 LoadStore 屏障。它是一个全能型屏障,它的开支比较大。
线程池描述一下使用方式或者场景
首先是 corePoolSize,还需要一个任务队列,还需要一个最大数。
特殊业务还需要设置一个饱和策略。RejectedExecutionHandler 。
其他参数,活跃时间等我都是走默认的。
如果队列满了,以及创建的线程数少于最大线程数,它还会创建新的线程。
corePoolSize 应该是在业务需求下创建的合适的值。
看需求情况去选无界队列还是有界队列。
饱和策略,有4种。抛出异常的,丢弃的,丢到另外队列的,调用的线程执行。
我们使用的是抛出异常。我们记录log信息。我们会存入db。我们会判断是否需要立刻处理,还是批处理。
redis
redis为什么快
它是内存型缓存。有人把它叫做db,它类似mongodb,非关系型结构。
它在内存,肯定比磁盘快。mysql最终需要 fsync 到硬盘。存储介质不一样,这是一个主要原因。
redis低层采用的是netty(同原理)
netty其实是一个单线程reactor模型。
客户端在使用时,会注册到select选择器里面,server端可以起单个线程,对select io到达处理。
线程后面接的是一个pipe handler管道,会对数据处理。
这种情况下,一台redis可以承载几万个客户端不敢说,1024个肯定没问题。
linux默认句柄数量是1024,生产服务器会调整到无上限。
你们使用什么内存淘汰机制。如何保证redis里都是热点数据。
redis里有一个是volatile的,两个是allkeys。
volatile有 volatile-lru volatile-ttl 和 random
lru 从以及过期的数据集里面挑选最近未使用的数据进行淘汰。
ttl 是从设置过期时间的数据集里面挑选即将要过期的数据
random 随机挑选数据集进行淘汰。
allkeys 一种是 allkeys-lru,移除最近最少使用的key。我们用的就是这
还有一种就是 allkeys-random 就是一个随机删除
还有一种内存不足写入直接报错。一般没人用。
视频3
这个视频看起来是个模拟面试。
候选人知道的挺多的,但是了解不够深入,像是背诵的。
面试官水平感觉不咋的,但是人不错。
前端小伙自学转Go开发面试中小厂录屏邀请各位来评分
https://www.bilibili.com/video/BV1oZ4y1R7hM/?vd_source=d695a68080dc880f4f71b97a852893c7
自我介绍
为什么从前端转后端
- 前端技术工具更新太快,来不及深入研究。
网络
了解 session cookie token 的区别吗
session服务器上,cookie浏览器上。
cookie不安全,session安全点
token用于认证
session cookie 哪个占用空间大(这什么sb问题)
- 没了解。cookie一般4kb
浏览器输入一个url到显示出来的过程
- 浏览器 ip -> host -> 本地dns -> 远程dns
- tcp 连接 -> http请求 -> 返回报文 -> 浏览器解析
http 1.0 1.1 2.0 新版本的特性
- 1.1 多了长连接,其他不了解
mysql
mysql 有哪些存储引擎,区别是什么
- innodb myisam
- 事务安全,支持行级别锁,
- 不支持事务,行锁
事务的特性
- acid 原子性 一致性 隔离性 持久性
了解acid底层怎么实现
- 原子性是 undo log
- 隔离性是 mvcc和锁
- 持久性是 undo log
- 一致性是 其他三个特性组成的
mvcc是什么
- 多版本控制管理(居然没有继续问实现原理。。。)
redis
redis的数据结构能说一下吗
- 简单动态字符串 双向链表 跳跃表 哈希表 压缩列表
用过哪些结果
- 登录次数用 string
- 登录次数排行用 有序列表
redis还提供可持久化功能,两种持久化方式了解过吗
- rdb还有 aof
- rdb 把数据用快照的形式保存起来
- aof 把每条指令都记录下来。完整性比rdb要好一些
redis缓存三大问题
- 雪崩,击穿和穿透。
- 雪崩 可以对redis做高可用部署。主从,哨兵集群方式。或者分布式锁。
- 穿透 提前做空值存储。或者布隆过滤器。
- 击穿 也可以用分布式锁解决。
雪崩一般是缓存大量过期,用分布式锁是解决不了的。怎么解决。
- 过期时间离散。或者key永不过期,用单独线程去更新。(面试官说回答不对,我觉得可以)
redis高可用一般怎么部署
- 主从+哨兵或者主从。具体不了。(可以看下一主二从三哨兵)
linux
软中断是什么
- 不了解(top命令里可以看到)
虚拟内存是什么
- 物理内存+交换页。 (回答不对)
虚拟内存的空间分布了解过吗
- 不了解
tcp和udp的区别
- tcp面向字节流,udp基于报文。可靠不可靠。udp一对多。
如何把udp变得可靠。
- 不了解。
go
slice 扩容
- 容量小于1024时,双倍扩容。大于1024是按1/4容量扩容。(面试官说不对)
channel为什么安全
- 内部维护了一个锁。
参数传递是什么传参
- 值传递。
引用类型一般是什么。内置的,常见的。
- channel map slice
写编程题
1到100从小到大,奇偶交替打印。
go 的gmp模型
- 调度模型。 协程 线程 调度器。
- p里面有g,m去执行g。p没有g,就会去全局队列里拿。
- 长时间阻塞的p会被解放出来。
go使用线程的时候会回收线程吗?
- 不太了解。
视频4
【模拟Go面试】模拟3年20K-30KGo岗位基础面
先自我介绍 + 项目介绍。三年经验,求职18K。
MySQL
有没有MySQL索引优化经验
对慢的查询语句,先排查日志。在测试环境中进行分析。
如果是比较频繁的语句,考虑加索引
看MySQL的CPU、内存占用,有没有其他的问题
根据SQL建立索引(没发出具体题目)
对abc三个字段建立联合索引。主要是利用到前缀的索引。
(最左原则)
(
追问:C能否用到索引
要为区分度大的建立索引
)
MySQL的索引结构
- B+树。多级树。
- 主键索引+非主键索引
B+树和B树有什么区别
- 没回答出来
RabbitMQ
为什么使用RabbitMQ进行日志采集
- 它的性能比较好。
- 公司讨论下来,kafka和RabbitMQ最终选了RabbitMQ
追问:但是Kafka可以达到每秒百万级别?
- RabbitMQ也够用了(回答不行)
为什么要用队列呢,不直接采集?采集的架构是什么样的?
- 机器上有个代理,监控日志文件,读取到RabbitMQ里面。
- 最后放到了ES里面。
- 队列起到了消峰的作用。我们的设备很多。消息队列可以缓冲。
RabbitMQ如何保证消息的顺序性。
- 主题是没有顺序。队列可以保证顺序。可以用同一个消费者进行消费。
- 生产者可以对指定队列进行发送。同一个消费者+同一个队列,可以保证顺序。(在乱说)
追问:消费者可能有多个线程。
- 这个没想过。可以起多个队列,对应多个消费者。
- 可以让它确认之后再消费下一个。(瞎说)
追问:RabbitMQ里有个配置吧。可以指定一个线程只能读取一个队列。
- 好像是吧。
Redis
redis主要是用来做什么?
- 缓存。缓存MySQL热点数据。
比如现在内存不足,有新的数据需要缓存,Redis会怎么处理
- Redis会进行淘汰。
淘汰策略怎么样
- 最常用的是LRU,还有LFU。
- 分为全局淘汰或者设置过期时间的数据进行淘汰。
缓存一致性怎么解决?
- 先更新数据库。再删除缓存里的数据。
你觉得这么做什么问题?
- 会增加延迟。会等数据库更新完,才读取到redis里。
- 另一个策略,先更新缓存,再更新数据库。
你的方案在并行环境下,可能会导致旧数据覆盖新数据。
- 可以考虑延迟双删。先删除缓存,更新数据。再删除缓存。(不对)
追问:还有其他方案吗?例如队列的方案。
- 没有采用。我们的方案,会比较简单。(回答不行)
GO
如果线上有内存泄露,你是怎么去处理的。
- pprof 查看火焰图和函数调用。从中去分析。看哪个函数会导致问题。
go是高并发的语言,它使用 CSP 模型实现并发,为什么要使用 CSP
- go的设计理念是不要使用共享内存,而是使用通信进行共享。
- go里通过channel进行数据共享。
channel是线程安全的吗?怎么保证的。
- 是的。底层有个锁。
channel的数据结构里有什么?
- 锁。队列。两个队列。等待队列
mutex是乐观锁还是悲观锁?
- 悲观锁。
rwmutex用过吗?
- 读写锁。高并发的时候用读写锁。
- 适合读多写少的情况。
map是不是线程安全的?
- 不是。会引起panic。
- 通常会加一个读写锁,或者使用 sync.map
现在想设计一个无锁保护的线程安全的map,怎么设计?
- 可以用队列。(不对)
- (可以用乐观锁,CAS操作思想)
go的结构体可以进行比较吗?
- 如果里面的字段是同一个类型可以进行比较。
空结构体一般用在什么情况下?
- channel发讯号会用。
- 定义一些类型,增加一些方法。
- (其实还有map空值)
channel为什么要用空结构体?
- 空的不占内存,节省空间。
字符串转byte数组会发生内存拷贝吗?
- 会的。字符串底层是常量不可改变。底层必须拷贝。
追问:转的适合不想发生内存拷贝,应该怎么做?
- 可以用 unsafe.Pointer 方法,把指向字符串的指针转成数组。(?)
CPU的cache如何保证一致?
- CPU一般有三级缓存。L1 L2是独占的,L3是共享的。
- 应该是底层有个硬件。和操作系统还不一样。(回答不对)
mesi协议有没有了解过?
- 没有
编程题
函数返回什么值?

- 应该是空值。因为在 defer 里申请了一个新的error。是一个新的命名空间(说的不对)
- (因为函数发生panic之后,后面的return代码没有执行,导致函数没有返回值。如果把 err error 声明到函数返回值里,就可以返回)
最后打印出来的C的值是多少?

- 2的64次方减1。因为计算机里的是补码的形式。
- (在现代计算机系统中,大多数系统都使用补码来表示有符号整数,因为这种方式可以使加法和减法操作都能用同样的硬件电路处理。)
输出是多少?

[100 200 3][101 102 103]
输出是多少?

[100 200 3][101 300 103]
- 因为
range
复制了原数组进行遍历。对于slice只会复制slice结构体,数据仍然读取自相同的引用数组。
GO
go的调度模型
- 候选人回答:GMP。goroutine。m是cpu线程。p是逻辑处理器,连接g和m。
以下是GMP模型的主要组成部分:
Goroutine (G):在Go语言中,每一个并发的任务都被称为一个Goroutine。每个Goroutine都有自己的栈和程序计数器,但Goroutine的大小远小于操作系统线程,更轻量级,所以在Go中创建和销毁Goroutine的成本非常低。
Machine (M):Machine代表了真正的执行计算的线程,可以理解为底层的操作系统线程。每个M都会关联一个G,然后执行G的任务。
Processor (P):Processor则代表了M需要的上下文环境,包括调度G的队列等。在Go的运行时系统中,P的数量是通过GOMAXPROCS来设置的,它决定了系统同时执行的Goroutine数量。
在GMP模型中,处理过程如下:
1 当Goroutine被创建时,它会被放到P的本地队列中。
2 每个P会尝试找到一个G(首先尝试从自己的本地队列找,如果找不到,再从其他P的队列中偷取),然后将G和M关联在一起。
3 M开始执行G的函数,直到G结束或者发生阻塞(如等待I/O或者系统调用)才会停止。当G结束或者被阻塞时,M会再次尝试找到新的G来执行。
这里面涉及抢占机制,可以解释一下?
- p里有个队列。如果队列满了,会放入全局队列。
编写go代码,使用goroutine和channel。有cat dog fish 3个函数打印对应的字符串。给每个函数创建一个goroutine,按cat dog fish顺序打印100次。
三色标记法
- 起初所有的对象都是白色的;
- 从根对象出发扫描所有可达对象,标记为灰色,放入待处理队列;
- 从待处理队列中取出灰色对象,将其引用的对象标记为灰色并放入待处理队列中,自身标记为黑色;
- 重复步骤3,直到待处理队列为空,此时白色对象即为不可达的“垃圾”,回收白色对象;
GC里的STW指的是什么
- Stop-the-world,简称STW,指的是GC事件发生过程中,会产生应用程序的停顿。
- 原因:为了保证准确性、防止无止境的内存增长等问题而不可避免的需要停止赋值器进一步操作对象图以完成垃圾回收。
小对象多了会造成GC的压力,为什么?
- 通常小对象过多会导致GC三色法消耗过多的CPU。优化思路是,减少对象分配
网络
说说三次握手和四次挥手
在计算机网络中,TCP/IP协议规定了一种称为"三次握手"的过程,它用于在两台设备之间建立连接,以及一种称为"四次挥手"的过程,用于断开连接。以下是这两个过程的详细描述:
三次握手(TCP连接的建立)
三次握手的主要步骤如下:
1 SYN步骤:客户端向服务器发送一个SYN(同步)包,请求建立连接。这个包中包含一个随机的序列号X。
2 SYN-ACK步骤:服务器收到SYN包后,返回一个SYN-ACK(同步确认)包。这个包中包含一个自己的随机序列号Y,和对客户端序列号X的确认(X+1)。
3 ACK步骤:客户端收到SYN-ACK包后,发送一个ACK(确认)包。这个包中包含对服务器序列号Y的确认(Y+1)。
完成三次握手后,客户端和服务器之间的连接就建立起来了。
四次挥手(TCP连接的终止)
四次挥手的主要步骤如下:
1 FIN步骤:当客户端完成了数据的发送,它会发送一个FIN(终止)包给服务器,请求断开连接。
2 ACK步骤:服务器收到FIN包后,返回一个ACK(确认)包,确认已收到断开连接的请求。但此时服务器可能还有数据需要发送,所以连接并未立即断开。
3 FIN步骤:当服务器发送完所有数据后,它会发送一个FIN(终止)包给客户端,请求断开连接。
4 ACK步骤:客户端收到FIN包后,返回一个ACK(确认)包,确认已收到断开连接的请求。然后等待一段时间(在这段时间内,如果客户端收到再次发送的数据,它还可以进行处理),再真正断开连接。
完成四次挥手后,客户端和服务器之间的连接就被断开了。
Linux
linux命令用过哪些
- cat sort grep ps 等
netstat用过吗
- 没有。用过 ss (ss命令是netstat的同类型工具,比netstat更新)
netstat里有 TIME_WAIT 和 CLOSE_WAIT 两个状态是指什么?
TIME_WAIT
:这个状态是TCP连接关闭过程的一部分。当本地系统关闭TCP连接时,这个连接将进入TIME_WAIT
状态,通常会在此状态下停留约60-240秒。这是为了确保TCP连接在完全关闭之前,对端系统能够接收到所有数据包。此外,TIME_WAIT
状态还能防止可能出现的“旧”重复数据包在新的连接中造成问题。CLOSE_WAIT
:这个状态表示远程端点(即对端系统)已经关闭了连接,本地系统正在等待关闭。如果你看到很多的CLOSE_WAIT
连接,这可能意味着有一些应用程序没有正确地关闭它们的连接,这可能会导致资源泄露。
TIME_WAIT 和 CLOSE_WAIT 哪个更可怕
- 大量的
TIME_WAIT
状态: 这可能意味着你的系统正在处理大量的短生命周期的连接。这通常在HTTP和其他请求/响应协议的服务器上较为常见。虽然TIME_WAIT
状态可能会消耗一些资源(如端口和内存),但是操作系统通常可以很好地处理这种情况。然而,如果TIME_WAIT
连接数量过多,可能会耗尽可用的网络端口,从而导致新的连接无法建立。 - 大量的
CLOSE_WAIT
状态: 这可能意味着一些应用程序没有正确地关闭它们的连接,这可能会导致资源泄露,如文件描述符和内存。如果这些连接长时间保持CLOSE_WAIT
状态,可能会耗尽服务器资源,导致新的连接无法建立,甚至服务器崩溃。因此,大量的CLOSE_WAIT
状态通常被视为一个更严重的问题。 - (面试者认为 CLOSE_WAIT更严重一点,意味着程序出了问题)
当客户端主动发起断开,服务端要去响应ACK,并主动关闭。服务端在等待的过程中是什么状态?
- 当客户端主动发起断开连接,服务端在接收到FIN(结束)标志后会发送一个ACK(确认)回应,并进入
CLOSE_WAIT
状态。在这个状态中,服务端已经接收到了关闭连接的请求,但是可能还在等待应用程序去关闭socket。
滑动窗口机制了解吗?
TCP的滑动窗口是用于控制发送方和接收方数据传输速率的一种机制,它允许接收方控制发送方可以在接收方确认之前发送多少数据。此机制基本上是为了防止发送方将过多数据发送给无法处理这些数据的接收方。
滑动窗口机制的核心思想是:发送窗口和接收窗口。发送窗口的大小决定了发送方在等待ACK之前可以发送多少数据,而接收窗口的大小则决定了接收方有多少空间用于存储接收的数据。
TCP的滑动窗口有三种状态:慢启动,防止拥塞,快速恢复。
慢启动(Slow Start):当TCP连接开始时,会先进入慢启动阶段,这时候发送窗口的大小会以指数级别增长,直到达到叫做慢启动阈值的值。
防止拥塞(Congestion Avoidance):当窗口大小达到慢启动阈值后,窗口的增长速度会降低,改为线性增长,这是为了避免网络的过度拥塞。
快速恢复(Fast Recovery):当TCP检测到包丢失时,它会重设慢启动阈值,然后进入快速恢复阶段。在这个阶段,窗口的大小会慢慢增长,直到恢复到丢包前的水平。
这就是TCP的滑动窗口机制的基本介绍,它是TCP流量控制和拥塞控制的重要组成部分。
RPC是什么
- RPC(Remote Procedure Call)是远程过程调用的简称,它是一种技术或者说是一种协议,使得运行在一台机器上的程序(客户端)可以像调用本地函数一样,调用运行在另一台机器(服务器)上的函数,而程序员无需额外考虑底层的网络通信细节。
RPC的工作流怎么设计?
- 客户端调用并传递参数:客户端程序调用本地的RPC库函数,就像调用本地函数一样,并传递参数。
- 参数打包并发送:RPC库将函数名和参数打包,然后通过网络发送到服务端。
- 服务端解析并执行:服务端的RPC库接收到请求后解析出函数名和参数,然后调用相应的服务端函数。
- 结果返回给客户端:服务端的函数执行完毕后,将结果返回给服务端的RPC库,RPC库将结果打包并发送回客户端。
- 客户端接收结果:客户端的RPC库接收到结果后解析并返回给客户端程序。
在这个过程中,会出现TCP的粘包问题。无法准确区分接收的数据。怎么解决?
- 候选人回答:用序列号,太底层,没有了解。
TCP的粘包问题是由于TCP是一个基于字节流的协议,而不是基于消息的协议,所以在TCP传输过程中,无法知道每个数据包的边界,从而可能会导致多个数据包被一起接收,也就是所谓的粘包问题。
解决TCP粘包问题的常用方法主要有以下几种:
固定长度:如果每个数据包的长度都是固定的,那么接收端可以根据固定的长度来接收数据,这样就能避免粘包问题。但这种方法的缺点是如果数据包的长度小于固定长度,就会造成带宽的浪费。
分隔符:在每个数据包的末尾添加一个特殊的分隔符,例如换行符'\n'或者其他特殊字符,接收端通过这个分隔符来识别每个数据包的边界。这种方法的缺点是需要保证数据包的内容中不会出现这个分隔符。
长度前缀:在每个数据包前面添加一个长度字段,用来表示数据包的长度,接收端先读取这个长度字段,然后再根据这个长度来读取相应的数据。这种方法是最常用的解决粘包问题的方法,因为它既能有效地解决粘包问题,又不会浪费带宽。
HTTP2 和 HTTP有什么区别
- 二进制协议:HTTP/2 是一个二进制协议,而 HTTP/1.x 是一个文本协议。这意味着 HTTP/2 可以使用更小的数据包和更少的数据传输来发送相同的信息,从而提高效率和速度。
- 多路复用:HTTP/2 支持多路复用,这意味着可以在一个 TCP 连接上同时发送和接收多个请求和响应。这消除了 HTTP/1.x 中的"队头阻塞"问题,即在一个 TCP 连接上,前一个请求必须完成,后一个请求才能开始。
- 头信息压缩:HTTP/2 引入了 HPACK 压缩算法,用于压缩 HTTP 头信息,以减少不必要的网络传输,这对于那些包含大量重复头信息的请求和响应来说是非常有用的。
- 服务器推送:HTTP/2 介绍了服务器推送(Server Push)机制,它允许服务器预先发送客户端可能需要的资源,而无需客户端明确地请求。
- 优先级和流量控制:HTTP/2 还引入了优先级和流量控制机制,允许更精细地控制数据的发送和接收。
面试反馈
go的GMP回答的不好,会导致聊不起来。
切片和数组地方有点问题。
mysql的索引经典题再琢磨琢磨。
本来想让你写一个LRU算法的,时间关系你自己写写。
CPU数据一致性问题,没有回答出来。这个高频。
还有一个题,设计一个无锁保护的MAP。高频。其实就是用乐观锁。