面试中高级golang开发工程师岗位,会问到什么?
今天分享的是训练营的一个朋友在成都某家公司的面经,求职岗位是中高级golang开发工程师,考察的内容很全面也很有难度,值得学习一下。
岗位职责:
-
理解业务需求,负责服务端的设计与开发;
-
与运维合作,部署维护自己的服务模块;
-
分析和深入发掘现有系统的不足,定位系统瓶颈,提高系统性能和稳定性。
任职要求:
-
计算机相关专业,本科以上学历,五年以上互联网行业后端开发经验,精通go语言;
-
对网络基础知识有一定了解,熟悉常见数据传输协议(如TCP,HTTP),了解分布式系统、队列及消息中间件;
-
具备微服务开发经验,熟悉功能逻辑分层的拆分方法;
-
积极主动,强烈的上进心和自驱力,具备良好的沟通能力、抗压力和团队协作精神。
收入结构:
月薪+年终奖金+绩效奖金+合伙年终分红+各类补贴+俊云特色福利包,全年累计14-16个月月薪。
面试纪要
1. 讲述一下项目的整体架构. 模块之间是怎么划分的,以及各模块的功能作用和它们之间的关系?
2. 在微服务体系下,不同服务之间的调用有哪些常见的方式?除了gRPC直接调用,是否还有其他异步的方式?
在微服务架构中,服务之间的调用主要有同步和异步两种方式。同步调用最常见的是通过HTTP/RESTful API或者gRPC来进行。异步调用则通常使用消息队列(如RabbitMQ、Kafka)来实现,这种方式的好处是解耦了服务之间的调用,提高了系统的可扩展性和可靠性。另外,事件驱动架构和发布订阅模式也是异步调用的典型例子。
3. 直接调用和异步调用之间各自的优缺点是什么?
直接调用的优点是简单直观,调用方可以直接获得结果,适合于实时性强的场景。缺点是如果被调用的服务出现问题,会直接影响调用方的正常运行。而异步调用的优点在于解耦和服务间的松散联系,即使某个服务挂了,也不会立即影响到其他服务。不过,异步调用的缺点是实现起来相对复杂,而且需要额外处理消息的可靠性和顺序问题。
4. 请从Producer、Broker、Consumer三个方面讲述一下怎么尽量保证消息不丢失?
Producer:发送消息时可以设置确认机制,确保消息成功发送到Broker;还可以使用事务消息,确保消息发送失败时可以回滚。
Broker:Broker需要持久化消息,即使Broker重启也能恢复未处理的消息;另外,可以通过配置冗余节点来提高可用性。
Consumer:消费消息时应该确保消息处理成功后再确认,避免消息丢失;可以设置重试机制,对于处理失败的消息进行多次尝试。
5. 不同微服务之间怎么保证数据的一致性?
为了保证微服务之间的数据一致性,可以使用分布式事务解决方案,如TCC(Try-Confirm-Cancel)、Saga、Seata等。另外,还可以通过消息队列实现最终一致性,即一个服务处理完业务逻辑后发送消息,另一个服务接收到消息后继续处理后续逻辑。
6. 讲一下TCC模式有哪些环节?假如扣减库存服务使用TCC时需要提供哪几个接口?
TCC模式分为三个环节:Try(尝试执行)、Confirm(确认执行)、Cancel(取消执行)。对于扣减库存服务,需要提供以下接口:
-
Try
:尝试扣减库存,检查库存是否足够。 -
Confirm
:确认扣减库存,真正减少库存数量。 -
Cancel
:取消扣减库存,恢复库存数量。
7. TCC不是还有一个try阶段吗,为什么只用提供2个接口?
在TCC模式中,Try
阶段的主要目的是检查业务规则是否满足,而不是真正执行业务逻辑。因此,在Try
阶段成功后,如果没有异常发生,Confirm
阶段会自动执行。如果Try
阶段失败,则会执行Cancel
阶段。所以,虽然有三个阶段,但在接口设计上通常只需要提供Confirm
和Cancel
两个接口。
8. 第三方协调者为什么不直接执行commit或者cancel,而是需要先去try,是为了解决什么问题?
第三方协调者先执行Try
阶段是为了确保各个服务的业务规则都满足,避免在Confirm
阶段执行时出现失败的情况。这样可以减少不必要的资源浪费,提高系统的可靠性和稳定性。
9. 有了解过二阶段提交和三阶段提交吗?
二阶段提交(2PC):
-
Prepare:协调者询问参与者是否可以执行事务提交。
-
Commit:如果所有参与者都同意,协调者发出提交命令;否则,发出回滚命令。
三阶段提交(3PC):
-
CanCommit:协调者询问参与者是否可以执行事务提交。
-
PreCommit:如果所有参与者都同意,协调者发出预提交命令。
-
DoCommit:如果所有参与者都预提交成功,协调者发出正式提交命令;否则,发出回滚命令。
三阶段提交相比二阶段提交增加了预提交阶段,减少了锁定资源的时间,提高了系统的可用性。
10. 了解DDD领域驱动设计吗?项目中是否有实际落地?实体、值对象、聚合的概念?
DDD(领域驱动设计)是一种软件开发方法,强调通过深入理解业务领域来设计软件系统。主要概念包括:
-
实体(Entity):具有唯一标识的对象,其身份在整个生命周期中保持不变。
-
值对象(Value Object):没有唯一标识的对象,其值相等即可认为是同一个对象。
-
聚合(Aggregate):一组相关对象的集合,通过聚合根来访问和管理。
在实际项目中,可以使用这些概念来更好地建模业务逻辑,提高代码的可维护性和可扩展性。
11. 什么是开闭原则?什么是依赖倒置?
开闭原则:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,新的功能应该通过增加新的代码来实现,而不是修改现有的代码。
依赖倒置:高层模块不应该依赖于低层模块,两者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。这样可以提高系统的灵活性和可复用性。
12. 除了MVC模式分层,有用过其他形式的分层架构吗?六边形架构、洋葱架构有了解吗?
六边形架构(Hexagonal Architecture):也称为端口和适配器架构,强调将业务逻辑与外部框架和工具解耦。通过定义清晰的接口(端口)和实现(适配器)来组织代码。
洋葱架构(Onion Architecture):类似于六边形架构,核心思想是将业务逻辑放在中心,外部层次围绕核心展开,每个层次只依赖于内层,不依赖于外层。这样可以更好地隔离变化,提高系统的可维护性。
13. 分层架构主要是用于解决什么问题?怎么利用分层架构来尽量降低业务和技术的复杂度?
分层架构主要用于解决代码的组织和管理问题,通过将系统划分为多个层次,每层负责不同的职责,从而提高代码的可读性和可维护性。利用分层架构可以:
-
明确职责分工:每一层只关注特定的任务,减少职责混乱。
-
降低耦合度:各层之间通过接口交互,减少直接依赖。
-
提高可测试性:每一层都可以单独进行单元测试,提高测试覆盖率。
14. MySQL事务隔离级别有哪些?是如何实现隔离级别的?脏读和幻读的区别是什么?
事务隔离级别:
-
Read Uncommitted:最低隔离级别,允许脏读、不可重复读和幻读。
-
Read Committed:允许不可重复读和幻读,但不允许脏读。
-
Repeatable Read:默认隔离级别,允许幻读,但不允许脏读和不可重复读。
-
Serializable:最高隔离级别,不允许脏读、不可重复读和幻读。
实现隔离级别的方法:
-
脏读:读取到未提交的数据。
-
不可重复读:同一事务中多次读取同一数据,结果不一致。
-
幻读:同一事务中多次执行同一查询,结果集不同。
15. 比如对一个查询语句加行锁,需要如何写SQL?
SELECT * FROM table_name WHERE id = ? FOR UPDATE;
这条SQL语句会在查询的行上加上排他锁,防止其他事务修改这些行。
16. 假如线上环境从监控数据看今天和昨天的MySQL流量并没有什么太大变化,但是MySQL一直在报连接数打满的问题,如何排查是什么原因导致的?
可以按照以下步骤进行排查:
-
查看当前连接数:使用
SHOW PROCESSLIST;
查看当前的连接情况。 -
分析慢查询日志:查看是否有长时间运行的查询语句。
-
检查连接池配置:确保连接池的配置合理,没有过多的空闲连接。
-
优化代码逻辑:检查是否有频繁创建和销毁连接的情况,尽量复用连接。
-
增加连接数限制:适当增加MySQL的最大连接数限制,但要注意资源消耗。
17. 从MySQL查询语句方面分析一下什么情况会导致连接池资源一直不释放?
-
长时间运行的查询:查询时间过长,导致连接无法及时释放。
-
死锁:多个事务互相等待对方释放资源,导致连接无法释放。
-
异常终止:客户端异常断开连接,导致连接池无法回收连接。
-
事务未提交或回滚:事务长时间未提交或回滚,占用连接资源。
18. MySQL怎么排查是否有出现死锁?
可以使用以下SQL语句查看死锁信息:
SHOW ENGINE INNODB STATUS;
在这条语句的结果中,查找 LATEST DETECTED DEADLOCK
部分,可以看到最近一次死锁的详细信息。
19. MySQL都有哪些日志,分别的作用是什么?
-
错误日志(Error Log):记录MySQL运行过程中的错误信息。
-
慢查询日志(Slow Query Log):记录执行时间超过指定阈值的查询语句。
-
二进制日志(Binary Log):记录所有的数据更改操作,用于数据恢复和主从复制。
-
通用查询日志(General Query Log):记录所有SQL语句,用于审计和调试。
-
事务日志(Redo Log):记录事务的物理修改,用于崩溃恢复。
20. MySQL一主多从的模式下,怎么保证master宕机的时候,主从切换过程中尽量不丢失数据?MHA有用过吗?
MHA(Master High Availability) 是一个用于MySQL高可用的工具,可以在主库宕机时自动切换到从库,尽量减少数据丢失。使用MHA时需要注意:
-
配置主从复制:确保主从之间的数据同步是实时的。
-
设置切换策略:配置MHA的切换策略,选择合适的从库作为新的主库。
-
监控和测试:定期监控主从状态,进行切换演练,确保切换过程顺利。
21. Redis有序集合的底层数据结构是什么?
Redis有序集合(Sorted Set)的底层数据结构是跳跃表(Skip List)。跳跃表是一种多层链表结构,支持高效的插入、删除和查找操作。
22. 假如使用数组实现,插入操作分为两步,第一步找到插入位置,第二步进行数据插入,那么找到位置的时间复杂度是什么?(有序数组,二分,log n)插入的时间复杂度是什么?
-
找到插入位置的时间复杂度:使用二分查找,时间复杂度为 (O(log n))。
-
插入数据的时间复杂度:在有序数组中插入数据需要移动后面的元素,时间复杂度为 (O(n))。
23. k8s是怎么实现滚动更新的?
Kubernetes(k8s)通过Deployment控制器实现滚动更新。具体步骤如下:
-
创建新的Pod副本:先创建新的Pod副本,确保新版本的Pod启动并准备好接收流量。
-
逐步替换旧的Pod:逐步停止旧版本的Pod,直到所有旧版本的Pod都被新版本的Pod替换。
-
回滚机制:如果新版本出现问题,可以快速回滚到旧版本。
24. 怎么排查内存泄露问题?pprof能具体定位到哪一行代码吗?一般什么样的写法会造成泄露?
排查内存泄露的方法:
-
使用pprof:通过Go内置的pprof工具收集内存使用情况,生成内存分析报告。
-
分析内存快照:查看内存快照,找出占用内存较多的对象。
-
定位代码:pprof可以生成调用栈信息,帮助你定位到具体的代码行。
常见的内存泄露原因:
-
循环引用:对象之间存在循环引用,导致垃圾回收器无法回收。
-
全局变量:全局变量长期持有对象引用,导致对象无法被回收。
-
未关闭的资源:文件句柄、网络连接等未正确关闭,占用内存。
25. Go语言的map是并发安全的吗?
Go语言的内置map不是并发安全的。如果多个goroutine同时读写同一个map,可能会导致数据竞争和程序崩溃。为了实现并发安全,可以使用 sync.Map
或者自己加锁。
26. 空结构体struct{}有什么作用?
空结构体 struct{}
占用的内存空间为0字节,常用于以下场景:
-
节省内存:在需要标记某个状态但不需要存储额外数据时,使用空结构体可以节省内存。
-
通道通信:在通道中传递空结构体,用于信号传递,表示某个事件的发生。
27. 算法题:假设有100w个int64类型的数字,其中有且只有2个重复数字,如何使用最优解法找出这个数字?大数排查问题,位图
最优解法:
-
异或运算:遍历所有数字,使用异或运算找出两个重复数字的异或结果。
-
分组异或:找到异或结果中任意一个为1的位,根据这一位将所有数字分成两组,每组分别进行异或运算,最终得到两个重复数字。
位图法:
-
创建位图:创建一个长度为64位的位图,每一位表示一个数字是否存在。
-
标记位图:遍历所有数字,将对应的位图位置1。
-
查找重复数字:再次遍历数字,如果发现某一位已经为1,则该数字是重复的。