系统设计面试速记
也许需要一套结构化话术以应对不同面试官拍脑袋的场景题。这套话术的核心并非提供完美的细节,而在于展示一种结构化的、由宏观到微观的思维方式,证明你理解复杂系统设计的核心矛盾与权衡。
DDD 架构设计
微服务与 DDD 架构设计的关系应当是密不可分的。DDD(领域驱动设计)通过限界上下文(Bounded Context)帮助我们划分微服务的边界,确保了服务的高内聚、低耦合。这是构建可独立演进、可独立扩缩容的微服务系统的基石。在高并发场景下,清晰的边界意味着我们可以针对性地优化某个核心服务,而不影响整个系统。
CQRS
CQRS (Command Query Responsibility Segregation,命令与查询职责分离) 是应对高并发场景的利器。其核心思想是将**改变系统状态的写操作(Command)与读取系统状态的读操作(Query)**在逻辑和物理上分离。
Command Side (命令端): 专注于接收写请求,执行业务逻辑,并持久化数据。可以为性能做极致优化,例如,接收请求后立即放入消息队列,快速响应用户,实现异步处理。
Query Side (查询端): 专注于数据 ...
数据库适配迁移方案设计
Gorm
gorm 身为 orm 框架,本身提供相当多的数据库操作函数,并会在底层自动将它们转成符合相应数据库 SQL 格式的 SQL。
gorm 重写(或者说转译)SQL 的类称为 Dialector。在具体执行方面可以复用 mysql 和 postgresql 的 Dialector,但是 mysql 语法转成 postgresql 语法这部分工作需要自行完成。
clause.OnConflict
mysql 与 pg 的 UPSERT 操作语法不一致:
1234-- MySQLINSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age); -- PostgreSQLINSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"=" ...
Turtle 分类与市场价格(2025 版)
种类
种属
寿命(年)
成体大小(cm)
龟苗价格保底(元)
饲养难度
冬眠
中华草龟
潮龟科拟水龟属
30+
雌 25/雄 15
0
较难,易腐皮腐甲
可
中华花龟
潮龟科拟水龟属
30+
25+
0
较难,易腐皮烂甲
可
日本石龟
石龟属
30-40
15-25
100
难,腐皮王中王
小青龟(北种石金钱龟)
拟水龟属
30-50
15-20
20
一般
可
黑颈龟
潮龟科拟水龟属
80
12-25
150
较难,杂交多需辨伪
可
巴西龟
泽龟科彩龟属
15-20+
25
0
容易,但易患白眼病
可
变异巴西龟
彩龟属变异种
15-25
20-30
50
一般,比普通巴西难养
可
火焰龟(纳尔逊伪龟)
泽龟科伪龟属
30-40
35-45
0
容易,发色后美观
可
黄喉拟水龟
潮龟科拟水龟属
40-50
15-20
0
较易,需白色环境发色
可
锦龟
泽龟科锦龟属
15-20
20
30
难,即使是成体也易无缘由暴毙
可
地图龟
泽龟科地图龟属
15-20
雌 15-25/雄 8-12
0
容易,精力旺盛
可
小鳄龟
鳄龟 ...
为面试疯狂,C++ 面试手写数据结构大全
前言
校招 C++ 大概学习到什么程度
写明白下面这几个代码 +能讲明白几个 C++11/14/17 的特性
MyString
MyVector
MyLRU
MySingleton
MyHashTable
MySharedPtr
MyUniquePtr
MyWeakPtr
MyThreadPool
MyRingbuffer
MyReadWriteMutex
MyForward
MyMove
MyString
展开代码
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465class MyString {private: char* data; size_t len;public: // 默认构造函数 MyString() : data(nullptr), len(0) {} // 构造函数 MyString(const ...
The Difference Between B-Tree, B+Tree, Red-Black Tree, LSM Tree and SkipList
人们总会争论 B-Tree 族、红黑树与跳表的互换性,已知 Java 与 C++ 的 Map 都使用了红黑树作为底层结构,围绕此问题,人们总会在一个缓存结构出现后问道:那为什么不使用跳表/B树呢?
rust 的 TreeMap 为什么采用 B-tree 而不是红黑树?
为啥 redis 使用跳表 (skiplist) 而不是使用 red-black?
抛开明明最有可能的个人作者喜好不谈(参与过开源的人都知道,项目最开始的走向极大概率取决于维护者个人喜好与品味,而后续的更改却需要拿出一个完善的 PR 与相当多的证据证明优越性才可能被维护者合入,简言之维护者乐意怎么写就怎么写,用哪个都有理,毕竟哪个都没有绝对优势),笔者在此总结一下这些 Tree 的特点。
名字
缓存友好性
调参友好性
并发友好性
实现 scan 操作友好性
insert/delete 实现难度
AVL Tree
不太友好,每个节点都 new 一遍太伤了
无参可调
不甚友好,旋转操作发生很频繁,要锁的节点太多
中序遍历,没 B+Tree 和 SkipList 友好
easy,谁都写过吧
Red-Bla ...
智能指针之说
RAII 与 unique_ptr
资源取得时机就是初始化时机(Resource Acquisition Is Intiallization,RAII)是 C++ 内存管理的第一课。
auto_ptr 自 C++98 提出,直到 C++17 被标准抛弃。后续被 unique_ptr 代替。auto_ptr 是历史遗留问题,是 C++ 的一处败笔,当然现在也已经被摈弃。
auto_ptr 对拷贝的处理方式是:隐式转移所有权。这不好,C++ 又不是有 GC 的语言,这种隐式容易导致程序员丧失对资源生命周期的掌控。
unique_ptr 自 C++11 提出,代替 auto_ptr,有效解决隐式转移所有权导致悬空的问题。对拷贝的处理方式是:不允许隐式转移所有权,必须显式移动语句才能转移所有权。
unique_ptr 在现代编译器的优化下基本是零开销(Zero Cost)的,换言之使用 unique_ptr 不仅可以享受到编译器单一所有权检查的功能,使用开销更是与裸指针无异。
unique_ptr 有什么特性,底层实现是怎样的,是怎么保证无法赋值构造的
shared_ptr 与 weak ...
如何假装自己是 GDB 糕手
GDB 是好的,笔者偶尔使用 GDB 调试大部分时间使用 Vscode/Clion 的按钮调试也是好的,只有说你平时用GDB吗?用IDE自带的?你基础不扎实啊。的面试官是坏的。
几句牢骚:抛开少数编译和启动时间就老长的大型项目不谈,校招生常见项目运行环境用个图形化界面怎么了?图形化界面本来每个按钮就和 gdb 命令一一对应,用熟了不是半斤八两?会个 gdb attach 就高贵了?
下面笔者尝试伪装自己平时对 vim 与 GDB 爱不释手,日常工作都是在没有图像化界面的 ArchLinux 上完成的,喜欢用萌萌哒语气,twitter 账号头像是粉蓝旗。
GDB 多线程调试小连招
命令
描述
示例
info threads
查看所有线程及状态
info threads
thread <线程ID> / t <ID>
切换到目标线程
thread 3
thread apply all bt
所有线程打印堆栈(排查死锁)
thread apply all bt
set scheduler-locking on
锁定当前线程,其他线程暂停
...
OS 与 PL Mutex 之三两事
仍然是面试速记。
死锁的 408 八股
考研 408 必背八股,谁都会问,但笔者全都以举例的方式逃过去了,不过还是记一下。
死锁的四大条件与预防死锁的四大方法(一一对应)
资源互斥,最不可能取消的条件,不互斥的资源自然也不会有同步需求,各管各的就好
持有并等待,反义词是检测到死锁就回退并重新获取锁,抛开极端情况不谈(时机恰恰好到无论怎么回退,两个资源争夺方都会以相同的顺序抢夺资源然后失败),这种方式比较好实现
不可剥夺,反义词是按某种规则检测到死锁后就老杀新或者新杀老,这样能保证同一时间一定有一个任务能被推进下去,被杀死的任务自动回退即可
环路等待,反义词是保持资源申请顺序一致,比如数据库给数据加行锁,无论是给哪条数据加,都要从第一条数据开始加,然后给每条数据依次加,不如直接加个大表锁,性能肯定更好,因此实践中其实比较难保证资源申请顺序一致。当然也有 B-tree 的 crabbing 协议这样的精巧的数据结构上锁方式。这个条件是基于持有并等待与不可剥夺才能成立的条件,一般来说无论是不满足持有并等待还是不满足不可剥夺,环路等待都将不成立。
避免死锁
预防死锁是设计静态的系统规 ...
设计模式速记
笔者是从所谓 组合优于继承 的 Golang、Rust 以及 Modern C++ 学起的,因此在不知不觉中跳过了 OOP 的相当部分学习,导致笔者在 OOP 的基础几乎不如刚经过期末考的大二学生,比如 OOP 的三大特性笔者就说不上来。
设计模式作为 OOP 的良好实践,笔者在没有学习之前已经看过了不少大型项目代码,因此也许能触类旁通罢。虽然从菜鸟教程来看,设计模式有多达数十种之多,但大家都知道 CS 最会造生词以增加初学者的学习门槛,常用的设计模式不超过十种。
对于 Javaer 的普遍技术水平不能报以太多期望,笔者在此对常用设计模式归纳总结一番以防 SB 面试官偷袭。
六大原则
开闭原则(Open Closed Principle,OCP),不要想着用子类修改父类,要改就用子类继承一套然后重写方法
单⼀职责原则(Single Responsibility Principle, SRP),一个类里写所有逻辑直接梦回面向过程
⾥⽒替换原则(Liskov Substitution Principle,LSP),保守的鸭子类型思想
依赖倒置原则(Dependency Inversio ...
消息队列笔记
数据传输语义
传输语义
说明
例子
at most once
最多一次:不管是否能接收到,数据最多只传一次。这样数据可能会丢失,
Socket,ACK=0
at least once
最少一次:消息不会丢失,如果接收不到,那么就继续发,所以会发送多次,直到收到为止,有可能出现数据重复
ACK=-1
Exactly once
精准一次:消息只会一次,不会丢,也不会重复。
幂等 + 事务 + ACK=-1
Acks
0,参考 UDP 协议,Producer 只管发。
1,只需要 Leader Broker 确认接收即可。熟悉分布式 DB 的朋友们都知道,没有多数 Follower 的 Commit 是不保证线性一致性的,这只是一个工程上的性能与正确性的 tradeoff。
-1 或者 all,需要多数 Follower 确认。
幂等性
引入 ProducerID 和 SequenceNumber,Producer 需要做的只有两件事:
初始化时像向 Broker 申请一个 ProducerID
为每条消息绑定一个 SequenceNumber
...