Appearance
日活/并发量
- 蝉管家日活:1-2K
- 蝉镜直播:70 个直播间
- 互动
- 话术衍生 TTS
- 蝉镜日活:1W+
- PC 端 4.8K+
- App 端 6.7K+
- 蝉镜作品数:3W+
- Jogg 日活:6K+
- Jogg 作品数:7K+
- 小型互联网产品
- 日活/流量:1k-10k 日活用户
- 并发量:100–500 并发请求/秒
- 技术需求:单机 + Nginx 反向代理 + MySQL/Redis,偶尔加个水平扩展就够
- 举例
- 一个小型在线投票网站,高峰期几百人同时投票,单台机器+缓存能轻松承载
- 某地方社区电商,每天几千订单,分布式集群完全没必要
- 内部工具(如考勤系统),并发几十请求/秒,普通数据库就能撑住
- 中型互联网产品
- 日活/流量:1w–100w 日活用户
- 并发量:500–5000 并发请求/秒
- 技术需求:
- 数据库读写分离
- Redis/Memcached 缓存
- 负载均衡 + 多机房部署
- 举例
- 一款区域电商活动秒杀,峰值 1,000 并发,缓存热点商品、用消息队列异步下单就能稳住
- 中型移动游戏登录/匹配系统,峰值并发 2,000,单机 MySQL+Redis + Nginx 就够,没必要微服务全套
- 企业级 SaaS 产品,某功能偶尔高峰并发 3,000,水平扩展数据库节点就能解决
- 大型互联网产品
- 日活/流量:100w+ 日活,峰值流量上亿请求/秒
- 并发量:几十万甚至上百万并发请求/秒
- 技术需求:
- 完全分布式架构
- 微服务 + API 网关
- 多级缓存(Redis、CDN、热点缓存)
- 消息队列 + 流量削峰(Kafka、RabbitMQ)
- 数据库分库分表 + 异地多活
- 举例
- 双 11 秒杀活动,峰值 200,000 并发,缓存、队列、降级策略必须到位
- 抖音短视频同时播放上百万次,CDN + 流媒体分发是标配
- 直播电商高峰并发 100,000+,微服务 + Redis + MQ + 数据库分库分表稳住整场
- 数据库读写分离 -> 原理:主从复制 -> 主从延迟
- 读写分离的路由方式
- RDS 读写分离代理
- GORM 对数据库读写分离(应用层)的支持 GORM DBResolver 会自动根据 SQL 类型路由
- 主从延迟
- 同步数据复制。默认是异步
- 强制读主:支付、订单相关业务
- 会话分离。让写会话暂时读主
- 参考资料
- 读写分离的路由方式
虾皮后端二面
TODO
数据流和控制流的分离
这玩儿可以写在简历上
为什么这样做?/有什么好处?
- 控制流主要做 CRUD。控制流炸了,会影响用户使用,但不影响用户的业务
- 数据流炸了的话会影响用户的业务
对于一个系统来说,影响用户业务是很严重的事情,而且我们日常变更最多的是控制流服务,控制流和数据流分离,使得我们发布变更控制流服务时,不会影响到数据流服务,即不会影响用户的业务
11 Labs 就是使用的这种架构。Jogg 使用 11Labs 的音色,经常遇到音色列表接口故障,但是 TTS 接口依然可以正常的使用,这就是数据流和控制流分离,因为对于用户来说,TTS 是核心业务逻辑
孔令飞推荐的高频 Go 面试题
- Go 语言中的并发和并行区别?
- 并发:同一时间段内处理多个任务(逻辑上的同时)
- 并行:同一时刻多个任务同时执行(物理上的同时)
使用 runtime.GOMAXPROCS(runtime.NumCPU()) 控制最大并行度,这样调度器就会让 goroutine 在多个 CPU 核上同时执行
Go 用 goroutine 实现并发,用多核调度实现并行
Go 调度器(GMP 模型)解决的问题:如何高效地在有限的线程(M)上调度成千上万个 goroutine(G)
- 进程、线程、协程的概念及区别;
进程,例如浏览器,微信,这些都是独立的进程,每个进程有独立的资源,例如内存、文件句柄等,通常不会相互影响。
线程,例如百度云网盘可以在下载文件的同时预览图片,对应多个线程在一个进程中。多个线程可以共享进程的资源,但也有自己独立的资源(例如栈)。线程是 CPU 调度的基本单元,上下文切换会产生开销(用户态->内核态)
上下文切换的开销:CPU 为了切换任务而保存/恢复执行现场、刷新缓存、重新调度所付出的时间与性能损失
协程不由操作系统调度,由语言运行时控制,例如 Go 的 GMP 调度器,用户态切换开销低
- Go 垃圾回收机制
- Go 的 GMP 调度原理
- Go Channel 通信原理
- 是什么?类似管道,用来在 goroutine 之间传递数据
- 有缓冲和无缓冲
- select 可以监听多个 channel:多路复用
- 阻塞 IO 和 IO 多路复用的区别
- Go 语言中, 接口是什么,为什么要使用接口
面向接口编程、多用组合少用继承
- 代码解耦、可扩展。不同的模块之间依赖接口,而不是实现
- 可测试。方便单元测试 mock 测试
- array 和 slice 区别和联系是什么,slice 的数据结构是什么,以及 slice 底层是如何扩容的
- Go 中 map 的实现原理是什么,map 是如何扩容的,如何解决 hash 冲突
- Go 语言参数传递时值传递还是引用传递?
- Go 语言中哪些是引用类型?
- defer 关键字的作用和用法
- 延迟执行函数,一般用于资源释放、日志处理、锁释放、事务回滚
- 坑点 1:多个 defer 的执行顺序:defer 会把函数调用 压入栈中,后进先出(LIFO)顺序执行
- 坑点 2:不管中途函数怎么退出(函数中间 return 或 panic),defer 都会执行
- 坑点 3:defer 调用时的参数会立即计算,函数本身延迟执行
- Go 语言的错误处理机制是什么?
函数显式返回错误,而不是抛出异常 try...catch
调用者必须主动检查,通过返回值让错误成为“普通控制流的一部分”,避免隐藏逻辑,也让程序行为更可预测
go
type error interface {
Error() string
}- errors.Join 合并错误
- errors.Is/As
- panic + recover
- 什么是类型断言?如何在 Go 语言中进行类型断言?
- 类型断言就是接口“拆箱”的工具
- 换个描述 1:这个接口值底层到底是什么类型,我要用它真实的类型来操作
- 换个描述 2:接口是类型+值的组合,类型断言能把接口“解包”为具体类型”
- 两种类型断言
- 单指断言。类型不匹配的话会 panic
- 安全断言。通过 ok 来确认断言成功/失败
- 类型断言和类型转换的区别
- Go 语言中如何实现读写锁?
- 读写锁:允许多个读并发执行,但是写操作时必须 独占
- 并行读;串行写;写时,不可读
- 适用场景:读多写少
- 举三个应用场景
- 缓存读取:例如蝉管家新手引导管理器,每 5 分钟从数据库同步一次配置到内存缓存,每个用户请求都会读取
拓展:Copy-on-Write(简称 COW,写时复制)
- 怎么做:复制新对象再替换
- 新旧版本覆盖、不会有脏读,没有锁竞争(无锁)
- 适用场景:读多写少
- 举三个应用场景
- 缓存读取:小数据结构直接 CoW,大数据结构使用读写锁