假期结束!上上强度!
时间过得真快啊,一眨眼假期就结束了,五天说长不长说短不短,不知道大家有没有出去旅游?还是呆在家里休息?(不会在公司加班吧??!!)
我反正是出去玩了一顿,不出去走走放松一下心情真的很容易把人憋坏。
正好,最近组织内的朋友给我分享了他在同程旅行一、二面的面经,想必大家放假的时候在学习上也懈怠了,我来给你们上上强度。
面经详解
一面
1. 介绍下什么是 #大对象小对象 #阈值是多少 区别在哪里 内存分配有什么不一样
大对象和小对象的划分是基于内存分配的粒度和策略。Go 的运行时系统(runtime)对不同大小的对象采用不同的内存分配方式,以优化性能和减少内存碎片。
阈值
小对象的阈值是 16B 到 32KB,而 大对象 是指大小超过 32KB 的对象。
小对象(16B~32KB):由 Go 的 mcache(线程缓存)分配。
大对象(>32KB):直接从堆(heap)中分配,绕过 mcache 和 mcentral,以避免碎片化。
区别
分配方式
小对象:通过 mcache(线程级缓存)分配,每个 P(逻辑处理器)对应一个 mcache,分配速度快,减少锁竞争。
大对象:直接从堆分配,由 Go 的 mheap(全局堆)管理,分配时可能触发 GC(垃圾回收)或调整堆大小。
内存碎片
小对象容易产生内存碎片,因为频繁分配和释放可能导致空闲内存块分散。
大对象通常一次性分配较大的连续内存块,减少碎片化风险。
性能优化
小对象的分配和回收效率较高,适合频繁创建和销毁的场景(如临时变量)。
大对象的分配成本较高,适合生命周期较长的对象(如大数组、大结构体)。
内存分配策略
Go 通过内存分级管理(mcache → mcentral → mheap)优化内存分配:
mcache:线程本地缓存,快速分配小对象。
mcentral:全局缓存,管理多个 mcache 的内存块,协调小对象分配。
mheap:全局堆,负责大对象分配和堆管理。
2. slice 的扩容机制
slice(切片)是一种动态数组,其底层依赖于数组和长度/容量信息。当 slice 的容量不足时,会触发扩容机制,具体规则如下:
扩容规则
容量小于 256:新容量为原容量的两倍。
容量大于等于 256:新容量会采用逐步增长策略,减少过度分配。
极端情况:如果扩容后仍不足,直接分配所需容量。
扩容过程
分配新的底层数组(大小为
newCap)。将旧数组的数据复制到新数组。
更新 slice 的
ptr(指向新数组)、len(长度)和cap(容量)。
3. 有缓冲 Channel 关闭后接受发送
有缓冲 Channel(buffered channel)关闭后:
发送操作
如果 Channel 已关闭,尝试发送数据会触发 panic(运行时错误)。
即使 Channel 仍有剩余容量,关闭后也不能再发送数据。
接收操作
关闭后仍可以接收 Channel 中剩余的数据。
当所有数据被接收后,后续接收操作会返回零值(zero value),并标记
ok为false。
4. 限流算法了解哪些
常见的限流算法包括以下几种:
令牌桶算法(Token Bucket)
原理:维护一个令牌桶,以固定速率向桶中添加令牌。请求需要消耗令牌,若无可用令牌则被拒绝。
优点:允许突发流量(令牌桶可积攒令牌)。
缺点:实现复杂度较高。
漏桶算法(Leaky Bucket)
原理:以固定速率处理请求,超出容量的请求会被丢弃或排队。
优点:平滑流量,防止突发流量冲击。
缺点:无法应对突发流量。
固定窗口计数器(Fixed Window Counter)
原理:在固定时间窗口内统计请求数量,超过阈值则限流。
缺点:存在临界点问题(如窗口边界处的请求突增)。
滑动窗口计数器(Sliding Window Counter)
原理:动态统计最近一段时间内的请求数量,解决固定窗口的临界问题。
优点:更精确地控制流量。
信号量(Semaphore)
原理:通过控制并发资源的数量实现限流。
优点:简单易用,适合资源池管理。
5. 说下令牌算法漏桶算法的优缺点
令牌桶算法和漏桶算法是两种经典的限流策略,它们的优缺点如下:
令牌桶算法
优点:允许突发流量,灵活性高。
缺点:实现复杂,需维护令牌数量和时间。
漏桶算法
优点:平滑流量,防止系统过载。
缺点:无法应对突发流量,可能造成请求堆积。
6. redis 怎么实现队列
Redis 可以通过其 List 数据结构实现队列,具体方法如下:
基础队列
入队:使用
RPUSH命令将元素添加到队列尾部。出队:使用
LPOP命令从队列头部取出元素。阻塞队列
阻塞出队:使用
BLPOP或BRPOP命令,若队列为空则阻塞等待。优先级队列
使用
ZSET(有序集合)实现优先级队列,通过分数(score)控制优先级。分布式队列
结合 Redis 的分布式锁(如
SETNX)实现多节点协作。
7. jwt 和传统 cookie、session 的区别
JWT 的适用场景
分布式系统(微服务架构)。
跨域认证(如 SPA 应用)。
传统 Session 的适用场景
单体应用或小型系统。
需要高安全性的场景(如银行系统)。
8. 说一下 etcd
etcd 是一个高可用的分布式键值存储系统,主要用于服务发现、配置管理、分布式锁等场景。
核心特性
强一致性:基于 Raft 协议实现分布式一致性。
高可用性:支持集群部署,容忍节点故障。
简单 API:提供 RESTful 接口和 Go 客户端库。
应用场景
服务注册与发现:存储服务实例的元数据。
分布式锁:通过租约实现锁的自动释放。
配置中心:集中管理分布式系统的配置。
二面
1. gc
Go 语言的**垃圾回收(GC)机制是基于标记-清除(Mark and Sweep)**算法的并发垃圾回收器,主要特点如下:
GC 流程
标记阶段:从根对象(全局变量、栈变量等)出发,遍历所有可达对象,并标记为“存活”。
清除阶段:回收未被标记的对象(垃圾)。
并发性
Go 的 GC 是并发执行的,与程序逻辑线程(M)并行运行,减少程序暂停时间。
使用三色标记法(白色、灰色、黑色)管理对象状态,避免标记错误。
低延迟设计
通过分代回收(Generational GC)优化,优先回收短生命周期对象。
写屏障(Write Barrier)确保标记阶段的准确性。
性能优化
STW(Stop-The-World):仅在标记阶段的初始和结束阶段短暂暂停程序。
内存分配:使用 mcache/mcentral/mheap 分级管理内存,减少 GC 压力。
2. 协程泄露
协程泄露(Goroutine Leak)是指 Goroutine 因未正确退出而持续占用资源,导致内存泄漏或资源耗尽。
常见原因
Channel 未关闭:
死循环未退出:
WaitGroup 未释放:
解决方案
使用 context.Context 控制 Goroutine 生命周期。
确保 Channel 在不再使用时关闭。
为死循环添加退出条件(如超时或外部信号)。
3. 三次握手四次挥手 为解决什么问题
三次握手和四次挥手是 TCP 协议建立和终止连接的核心机制,用于解决以下问题:
三次握手(建立连接)
目的:确保双方都能正常收发数据,避免因重复连接请求导致混乱。
流程:
SYN:客户端发送连接请求。
SYN-ACK:服务器确认请求并回复。
ACK:客户端确认连接成功。
四次挥手(终止连接)
目的:确保双方完成数据传输后安全关闭连接,避免半开连接。
流程:
FIN:主动关闭方发送结束请求。
ACK:被动关闭方确认。
FIN:被动关闭方发送结束请求。
ACK:主动关闭方确认。
关键问题
避免重复连接(三次握手)。
确保数据完整性(四次挥手)。
4. 粘包 原理及解决
粘包(Sticky Packet)是 TCP 协议中因流式传输导致的数据边界模糊问题。
原理
TCP 是流式协议,不维护消息边界。发送端连续发送的数据可能被接收端合并为一个或多个数据包。
示例:发送两个消息
"ABC"和"DEF",接收端可能收到"ABCDEF"(粘包)。解决方法
固定长度:每条消息固定长度(不足时填充空字符)。
自定义协议头:在消息前添加长度字段。
特殊分隔符:使用特定字符(如
\n)分隔消息。
5. 自定义一个协议,怎么知道要分配多大一个内存去存放接收的数据包
在自定义协议中,确定接收数据包的内存分配策略需要结合协议设计:
步骤
解析协议头:在协议头中包含数据长度字段(如 4 字节的 BigEndian 整数)。
动态分配内存:根据长度字段分配缓冲区。
预分配缓冲区:对于固定长度的消息,预先分配固定大小的缓冲区。
注意事项
避免缓冲区溢出:限制最大消息长度(如
MAX_PACKET_SIZE)。处理分片:如果数据包分多次到达,需缓存未接收部分。
校验数据完整性:通过校验和或 CRC 验证数据是否完整。
欢迎关注 ❤
我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。
没准能让你能刷到自己意向公司的最新面试题呢。
感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。
版权声明: 本文为 InfoQ 作者【王中阳Go】的原创文章。
原文链接:【http://xie.infoq.cn/article/6ae51257fae7fb3a4a18f9dba】。文章转载请联系作者。









评论