CS300 (CSCI 0300: Fundamentals of Computer Systems,计算机系统基础) 是一门布朗大学的课程,和 CMU 的 15-213 类似,前半段围绕 C 语言、汇编、内存管理等,后半段则是网络、分布式系统、并发相关,也以 CSAPP 为教材。3e 书本自带的 Lab,太过古老了(更新于 20162019)也没兴趣做了~,所以 Proxy Lab 被跳过,虽然与 Proxy Lab 关系不大在此补上一个感兴趣的并发与分布式相关的 Project 5 KVstore。
Project 5 KVstore 分为 5A 和 5B 两个部分,分别对应并发和分布式两个主题。
5A Concurrent KVStore
5A 分为两个 Part,Part 1 分为四个 Steps 逐步实现一个并发桶存储 KVStore:
-
Step 1: 实现一个简单的单线程 KVStore,支持
get、put、delete、append、multiget、multiput、allkeys操作。 -
Step 2: 实现一个粗粒度锁的并发 KVStore,只使用一个全局锁来保护整个 KVStore。
-
Step 3: 将 KVStore 分为多个桶,为下一步做准备。
-
Step 4: 为每个桶加锁,适配各个操作,最终实现一个细粒度锁的并发 KVStore。
其中值得一提的,除了记得为 ,是对于 1 -> 2 -> 3 和 2 -> 1 -> 4 这种循环死锁的情况,可以按照固定统一的顺序加锁(比如按照桶的 ID 顺序),这样就可以全都变成 1 -> 2 -> 3 -> 4 的顺序,避免死锁。(前提是预知要加哪些锁,提前进行排序;或者需要加锁时,业务产生的加锁需求本身就是固定顺序的,则不需要预知。并非银弹)Delete 操作的 res 响应填入被删除的 value 以外
主要是冲着并发、锁相关的知识来的,15-445 的经历太折磨了…
至于 Part 2 是调用这些逻辑,实现一个 GDPRDelete,非常冗长,全责交给 Gemini 了…直接删除所有 user 发布的贴子和回复。
5B Distributed KVStore
5B 则是实现分布式 KVStore,主要是划分 key 的字典序范围,分配到多个 KVStore 节点上。
以下内容摘自课程:
- A shardcontroller, which determines the shards and stores which servers are responsible for each shard,
碎片控制器负责确定各个碎片的分配情况,并记录下哪台服务器负责处理每个碎片。 - A sharding-aware server which periodically queries the shardcontroller to determine if any of its shards have been moved to another server, and if so, moves the key-value pairs for that shard to the new server, and
这种服务器具备分片感知功能:它会定期向分片控制器查询,以确定是否有某个分片被移动到了另一台服务器上。如果确实如此,该服务器会将该分片对应的键值对同步到新的服务器上。 - A sharding-aware client which, for each request, queries the shardcontroller to determine which server(s) have the key(s) relevant to its request, then directs its request to those server(s).
这种具备分片处理功能的客户端在处理每个请求时,都会先向分片控制器查询,以确定哪台或哪些服务器拥有与该请求相关的键值数据。之后,该客户端会将请求发送到这些服务器上。
由 shardcontroller 维护分发一个 ShardControllerConfig,客户端根据这个 ShardControllerConfig 计算 key 所在的 server 去发送请求,服务器持续请求新的 ShardControllerConfig,如果发现自己不再负责某个 shard,就将该 shard 的数据迁移到新的服务器上(客户端<->服务端、服务端<->服务端都是直接通信传输,shardcontroller 仅作为配置维护,没有请求转发和数据转移的功能)。
测试结果
1 | cs300-user@731e8e338cf4:~/cs300-s26-projects/kvstore/build$ make check |
注意到 Performance Test Results 提供了两份结果,后一份是在 TSAN=1 的情况下,启用了 Thread Sanitizers 的测试结果,会发现性能下降了接近十倍,在启用的情况下,我的 A5 kvstore_performance_tests 实际上是无法通过的,TLE。

总结
整个 Project 5 显然是比较简单的,一点也不复杂,提供的配套代码已经很完善了,对 TODO 进行小修小补填充就算完成了,并没有多少工作量。
有个机制很奇怪,KVStore 节点里面是按 hash 分桶的,但是 shardcontroller 却是按字典序直接分片的,伴随着的是一大坨 split_into。我举得直接一点,将 桶直接分配到多个节点上显然会更简洁一点,也更适应 Extra Credit 提出的动态分片的需求。
头一次知道有 TSAN 这种东西,查了下大致是通过线程对内存的读写顺序判断数据竞态,以及锁的加解和信号量等相关的并发控制来判断是否是同步执行的,筛选掉安全的操作,对潜在的竞态进行报告。我挺好奇它具体如果进行判断的,以及测试框架如何判断没有出现竞态等并发问题。
以及为什么 CMU 15-445 没有告诉我有这种工具,只说了有 ASAN 来检测内存泄露…我想可能应该有用的啊
以及实际上我觉得缺少了很多内容,虽然它也不是专门学分布式系统的课程:
-
控制器只下发配置,并不考虑执行效果。服务端通过循环请求来获取配置,没有回报生效后才客户端可见的功能,而客户端本身却直接根据最新配置去请求服务端。在服务端与服务端交换数据的期间,会出现数据不可达的情况
-
没有考虑数据交换的耗时,交换期间会直接锁死整个节点,无法处理请求
忽略太多东西了,所以并没有特别多学到什么,一天 5A 一天 5B,摸摸鱼就完成了。
Project 5 实现见:z0z0r4/cs300-s26-projects
后话
不太满意这个 speedup 3x 和吞吐量,遂在出门吃早餐时丢给小蓝鲸试着优化锁,虽然给出更好的锁设计,但是它将 bucket 课程自带的 std::list 换成了 std::unordered_map 之后,速度提升了大概 50x,crazy…
这显然不能赖我没注意到,这是默认的,不是我写的,我压根没印象
1 | - std::array<std::list<DbItem>, BUCKET_COUNT> buckets; |
z0z0r4/cs300-s26-projects/commit/ecd7f8eaadc29b5442f1f7d4b4cbe31bc91f5ef2
跑出来的时候满屏幕的绿色给我吓了一跳.jpg
1 | cs300-user@731e8e338cf4:~/cs300-s26-projects/kvstore/build$ make perf |

至于依旧只有 3x,考虑到我的 CPU 也不过四核心,好像也合理?
1 | cs300-user@731e8e338cf4:~/cs300-s26-projects/kvstore/build$ bash -c 'echo "=== CPUs ===" && nproc && echo "=== CPU info ===" && cat /proc/cpuinfo | grep -E "processor|model name|cpu cores|siblings|physical id" | head -20 && echo "=== lscpu ===" && lscpu 2>/dev/null | head -20 || echo "lscpu not available"' |
后后话
晚上发现 kvstore/tests/kvstore_performance_tests/test_performance_put_get.cpp 的注释里面这样写着:
1 | // Generate map of random keys. |
现在回想起来,大半夜的不清醒了,3x 挺合理的…
第一点我高估了多线程的效果,保证原子性的的情况下,如果多个请求读写冲突,就应该退化到单线程的性能,所以说如果这个性能测试是针对一个恶意负载,那么就不应该是有 3x 或者 4x 的提升,应该是退化到低于单线程的性能才对(由于锁的开销)。
第二点是根据上面的注释和测试代码可见,为了体现出多线程的效果,是构造了完全不会冲突的负载,设定为开 8 线程,每个线程访问不同的桶,理论上是最大 8 倍的提升,最后结果是 3x 上下的提升,考虑各种开销也合理,没问题…
昨晚觉得是因为只有四个核心,多几个核心说不定就有更大提升了。
下午找 pysio 借到了台 16 cores 的 VPS 跑了下测试,发现还是最多跑出来 5x~7x 的 speedup,怀疑是自己写的锁的限制。

pysio tks 呜呜呜!~

甚至跑出来 2x,perf 乱抖,陷入歧路有点迷了
晚上在图书馆看 OSTEP,偶然想起来看一眼 make perf 的源码,最后才意识到是写死了 8 线程…然后再一想啥都通了…
1 | // warning: N_THREADS must be < 10 because of hash function |
我是🐖,我收回没啥收获这句话,虽然简单,但培养直觉了…
我连直觉都没,大言不惭了
TODO:寻找一个正式的分布式课程,找到 一份大佬翻译的 MIT 6.824 Notes
说些什么吧!