面试中高级golang开发工程师岗位,会问到什么?

今天分享的是训练营的一个朋友在成都某家公司的面经,求职岗位是中高级golang开发工程师,考察的内容很全面也很有难度,值得学习一下。

岗位职责:

  1. 理解业务需求,负责服务端的设计与开发;

  2. 与运维合作,部署维护自己的服务模块;

  3. 分析和深入发掘现有系统的不足,定位系统瓶颈,提高系统性能和稳定性。

任职要求:

  1. 计算机相关专业,本科以上学历,五年以上互联网行业后端开发经验,精通go语言;

  2. 熟悉mysql、redis、MongoDB等数据库,具备高并发实时数据处理接口的设计和开发经验优先;

  3. 对网络基础知识有一定了解,熟悉常见数据传输协议(如TCP,HTTP),了解分布式系统、队列及消息中间件;

  4. 具备微服务开发经验,熟悉功能逻辑分层的拆分方法;

  5. 积极主动,强烈的上进心和自驱力,具备良好的沟通能力、抗压力和团队协作精神。

收入结构:

月薪+年终奖金+绩效奖金+合伙年终分红+各类补贴+俊云特色福利包,全年累计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阶段。所以,虽然有三个阶段,但在接口设计上通常只需要提供ConfirmCancel两个接口。

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一直在报连接数打满的问题,如何排查是什么原因导致的?

可以按照以下步骤进行排查:

  1. 查看当前连接数:使用 SHOW PROCESSLIST; 查看当前的连接情况。

  2. 分析慢查询日志:查看是否有长时间运行的查询语句。

  3. 检查连接池配置:确保连接池的配置合理,没有过多的空闲连接。

  4. 优化代码逻辑:检查是否有频繁创建和销毁连接的情况,尽量复用连接。

  5. 增加连接数限制:适当增加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控制器实现滚动更新。具体步骤如下:

  1. 创建新的Pod副本:先创建新的Pod副本,确保新版本的Pod启动并准备好接收流量。

  2. 逐步替换旧的Pod:逐步停止旧版本的Pod,直到所有旧版本的Pod都被新版本的Pod替换。

  3. 回滚机制:如果新版本出现问题,可以快速回滚到旧版本。

24. 怎么排查内存泄露问题?pprof能具体定位到哪一行代码吗?一般什么样的写法会造成泄露?

排查内存泄露的方法

  • 使用pprof:通过Go内置的pprof工具收集内存使用情况,生成内存分析报告。

  • 分析内存快照:查看内存快照,找出占用内存较多的对象。

  • 定位代码:pprof可以生成调用栈信息,帮助你定位到具体的代码行。

常见的内存泄露原因

  • 循环引用:对象之间存在循环引用,导致垃圾回收器无法回收。

  • 全局变量:全局变量长期持有对象引用,导致对象无法被回收。

  • 未关闭的资源:文件句柄、网络连接等未正确关闭,占用内存。

25. Go语言的map是并发安全的吗?

Go语言的内置map不是并发安全的。如果多个goroutine同时读写同一个map,可能会导致数据竞争和程序崩溃。为了实现并发安全,可以使用 sync.Map 或者自己加锁。

26. 空结构体struct{}有什么作用?

空结构体 struct{} 占用的内存空间为0字节,常用于以下场景:

  • 节省内存:在需要标记某个状态但不需要存储额外数据时,使用空结构体可以节省内存。

  • 通道通信:在通道中传递空结构体,用于信号传递,表示某个事件的发生。

27. 算法题:假设有100w个int64类型的数字,其中有且只有2个重复数字,如何使用最优解法找出这个数字?大数排查问题,位图

最优解法

  1. 异或运算:遍历所有数字,使用异或运算找出两个重复数字的异或结果。

  2. 分组异或:找到异或结果中任意一个为1的位,根据这一位将所有数字分成两组,每组分别进行异或运算,最终得到两个重复数字。

位图法

  1. 创建位图:创建一个长度为64位的位图,每一位表示一个数字是否存在。

  2. 标记位图:遍历所有数字,将对应的位图位置1。

  3. 查找重复数字:再次遍历数字,如果发现某一位已经为1,则该数字是重复的。

早日上岸!

4