MIT 6.824 Lab 1学习笔记
Lab 1
理论部分
节选摘抄
- MapReduce出现的契机
在过去的五年时间里,包括作者在内的许多谷歌工作人员实现了数以百计的、用于特殊目的的计算程序来处理大量的原始数据,例如爬虫获取到的文档、网络请求日志等等。
其目的是为了计算出各种类型的衍生数据,例如倒排索引、多种关于web文档的图结构表示、被每个主机所爬取的页面数摘要、给定的某天中被最频繁查询的集合等等。
大多数这样的计算在概念上都很简单,然而输入的数据却通常是巨大的。而且为了能在一个合理的时间范围内完成,计算操作需要被分配到数百甚至数千台机器上运行。
关于如何并行计算,如何分派数据以及如何处理故障等问题被混杂在了一起,使得原本简单的计算逻辑被用于处理这些问题的大量复杂代码所模糊为了应对这些复杂性,我们设计了一个全新的抽象,该抽象允许我们表达我们想要执行的简单计算,但是将关于并行化、容错、数据分发和负载均衡等机制中复杂、繁琐的细节隐藏在了库中
- 技术模型
这一计算获得并输入一个k/v键值对集合,然后生成并输出一个k/v键值对集合。
MapReduce库的用户通过Map和Reduce这两个函数来表达该计算逻辑。Map函数是由用户编写的,其获得一个输入的k/v对并生成一个中间态的k/v对
MapReduce库对所有的k/v对进行分组,使得所有有着相同中间态key值的k/v对的value值组合在一起,然后将它们传递给Reduce函数。Reduce函数也是由用户编写的,其接收一个中间态的key值和与该键对应的一组value值的集合。
它会将这些value值进行统一的合并以形成一个可能更小的value值集合。
通常,每次reduce调用只会生成零个或一个输出值。这个中间态的value集合通过一个迭代器提供给用户的reduce函数。
这允许我们得以处理那些无法被完整放入内存的,过大的列表集合。
架构图
摘抄自论文
实践部分
准备工作
克隆代码仓库
$ git clone git://g.csail.mit.edu/6.5840-golabs-2026 6.5840
$ cd 6.5840检查已实现的源代码能否运行
检查单词计数器
原版指令是给Unix Go编译器用的:
$ cd ~/6.5840
$ cd src/main
$ go build -buildmode=plugin ../mrapps/wc.go直接运行会得到:
F:\GolandProjects\6.5840\src\main>go build -buildmode=plugin ../mrapps/wc.go
-buildmode=plugin not supported on windows/amd64我把wc.go迁移到了src/main/plugins/official/mr下,并修改源代码中的package,以及mrsequal.go代码中的CLI用法示例和判定条件,改为利用go package导包(要切换包的话就自己改目录路径)
然后是通配符语法,pg*.txt的通配符语法在Windows环境不起效,好在filepath库提供了GlobAPI,所以:
for _, filename := range os.Args[1:] {
matches, err := filepath.Glob(filename)
if err != nil {
log.Printf("模糊搜索%v文件时发生异常: %v", filename, err)
continue
}
if len(matches) == 0 {
log.Printf("未找到%v文件", filename)
continue
}
for _, matched := range matches {
content, err := os.ReadFile(matched)
if err != nil {
log.Printf("读取文件%v时发生异常: %v", matched, err)
continue
}
kva := mapf(filename, string(content))
intermediate = append(intermediate, kva...)
}
}然后:
# src/main
go run mrsequal.go pg*.txt
rm mr-out-*测试样例也是,要么重写要么上Unix
实现模块
注意
合并分支时出现了索引污染,我果断删除git仓库,但是忘记物理备份源代码,所以源代码全部都没了
RPC传参结构体
Coordinator
Worker
最终成果

左边是随机崩溃测试后的sort输出, 右边是单worker测试后的sort输出

左边是mrsequal.go输出,右边是随机崩溃测试后的sort输出
会多出一些单词,不过总体顺序是对的
Lab 2:实现KV系统的服务与客户端
理论部分
实践部分
GET和PUT实现
分布式锁
问题排查
调用RPC方法时没有带上完整方法名
└─# cd kvsrv1 && go test -v -race -run Reliable
=== RUN TestReliablePut
One client and reliable Put (reliable network)...
panic: runtime error: slice bounds out of range [:-1]
goroutine 51 [running]:
6.5840/labrpc.(*Server).dispatch(0xc00011e2c0, {0x853cc0?, 0xc00021e3a0?}, {{0x853cc0, 0xc00021e380}, {0x8ca199, 0x3}, {0xc000238080, 0x43, 0x80}, ...})
/develop/6.5840/src/labrpc/labrpc.go:520 +0x685
6.5840/labrpc.(*Network).processReq.func1()
/develop/6.5840/src/labrpc/labrpc.go:314 +0xc7
created by 6.5840/labrpc.(*Network).processReq in goroutine 50
/develop/6.5840/src/labrpc/labrpc.go:313 +0x315
exit status 2ck.clnt.Call函数的第二个参数method string需要的完整的方法名,即包括服务名称的方法名,如KVServer.Get
clnt的server不等于ServiceName
┌──(root㉿kali)-[/develop/6.5840/src]
└─# make RUN="-run Reliable" kvsrv1
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
cd ..
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
=== RUN TestReliablePut
One client and reliable Put (reliable network)...
2026/04/08 06:55:16 labrpc.Server.dispatch(): unknown service server-0-0 in server-0-0.Put; expecting one of [KVServer]
kvsrv_test.go:24: Put err Connection Failed
2026/04/08 06:55:16 checkpointPersister: no return
exit status 1
FAIL 6.5840/kvsrv1 0.276s
make: *** [Makefile:23: kvsrv1] Error 1
=== RUN TestReliablePut
One client and reliable Put (reliable network)...
2026/04/08 06:55:20 labrpc.Server.dispatch(): unknown service server-0-0 in server-0-0.Put; expecting one of [KVServer]
kvsrv_test.go:24: Put err Connection Failed
2026/04/08 06:55:20 checkpointPersister: no return
exit status 1
FAIL 6.5840/kvsrv1 0.392s- 测试样例期望的方法名中的服务名参数应该是
KVServer,而不是server-0-0这个组ID

直接用KVServer作为服务名即可
自制KV模块没有正确初始化
┌──(root㉿kali)-[/develop/6.5840/src]
└─# make RUN="-run Reliable" kvsrv1
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
cd ..
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
=== RUN TestReliablePut
One client and reliable Put (reliable network)...
panic: runtime error: index out of range [0] with length 0
goroutine 43 [running]:
6.5840/kvsrv1/cache.(*MapStorage).Put(0xc0002220c0, {0x76f6f8, 0x1}, {0xc0001163d8, 0x6, 0x8}, 0xc0001323c8?)
/develop/6.5840/src/kvsrv1/cache/utils.go:38 +0x565
6.5840/kvsrv1/cache.(*MapStorage).TryPut_.func1()
/develop/6.5840/src/kvsrv1/cache/utils.go:96 +0xa5
created by 6.5840/kvsrv1/cache.(*MapStorage).TryPut_ in goroutine 42
/develop/6.5840/src/kvsrv1/cache/utils.go:95 +0x2cb
kvsrv_test.go:24: Put err Connection Failed
2026/04/08 07:00:28 checkpointPersister: no return
exit status 1
FAIL 6.5840/kvsrv1 0.394sbuild方法中,遍历结构的初始化参数是Partition的长度,但Partition此时还是0长度,所以没有初始化成功,一访问就数组越界Panic
Get方法设计存在问题
┌──(root㉿kali)-[/develop/6.5840/src]
└─# make RUN="-run Reliable" kvsrv1
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
cd ..
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
=== RUN TestReliablePut
One client and reliable Put (reliable network)...
kvsrv_test.go:30: Get value err ; expected 6.5840
--- FAIL: TestReliablePut (0.19s)
=== RUN TestPutConcurrentReliable
Test: many clients racing to put values to the same key (reliable network)...
Fatal: Unmarshal err 1Fatal: Unmarshal err 1
/develop/6.5840/src/kvtest1/kvtest.go:185
/develop/6.5840/src/kvtest1/kvtest.go:185
/develop/6.5840/src/kvtest1/kvtest.go:226
/develop/6.5840/src/kvtest1/kvtest.go:286
/develop/6.5840/src/kvsrv1/kvsrv_test.go:62
/develop/6.5840/src/kvtest1/kvtest.go:156
/develop/6.5840/src/kvtest1/kvtest.go:226
/develop/6.5840/src/kvtest1/kvtest.go:286
/develop/6.5840/src/kvsrv1/kvsrv_test.go:62
/develop/6.5840/src/kvtest1/kvtest.go:156Get方法的实现是直接把Bucket的Value取出来扔出去,可能是堆栈逃逸没成功?导致了空切片问题type Bucket struct { mu sync.RWMutex Value []byte Index uint64 // 版本索引 } type Pairs struct { mu sync.RWMutex Buckets map[uint64]*Bucket }
无题
┌──(root㉿kali)-[/develop/6.5840/src]
└─# make RUN="-run Reliable" kvsrv1
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
cd ..
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
=== RUN TestReliablePut
One client and reliable Put (reliable network)...
2026/04/08 10:29:31 [DEBUG] 正在查询: k; 哈希值为 13424616522440641142, 归属分区 0, 准备进行变更操作
2026/04/08 10:29:31 [ERROR] 查询不到给定key[k], 尝试添加KV
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x6b95ec]
goroutine 29 [running]:
6.5840/kvsrv1/cache.(*MapStorage).Put(0xc0000a9350, {0x771918, 0x1}, {0xc00011c388, 0x6, 0x8}, 0x0)
/develop/6.5840/src/kvsrv1/cache/utils.go:68 +0xdec
6.5840/kvsrv1/cache.(*MapStorage).TryPut_.func1()
/develop/6.5840/src/kvsrv1/cache/utils.go:117 +0xa5
created by 6.5840/kvsrv1/cache.(*MapStorage).TryPut_ in goroutine 28
/develop/6.5840/src/kvsrv1/cache/utils.go:116 +0x2cb
kvsrv_test.go:24: Put err Connection Failed
2026/04/08 10:29:31 checkpointPersister: no return
exit status 1
FAIL 6.5840/kvsrv1 0.253s
make: *** [Makefile:23: kvsrv1] Error 1发生在Put方法里,是因为打日志时用的Bucket值是未初始化的,而赋值的对象是数组元素,访问Bucket的Index属性时触发了空指针异常
Copy方法莫名其妙不可用
┌──(root㉿kali)-[/develop/6.5840/src]
└─# make RUN="-run Reliable" kvsrv1
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
cd ..
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
=== RUN TestReliablePut
One client and reliable Put (reliable network)...
2026/04/08 10:37:15 [DEBUG] 正在查询: k; 哈希值为 16323581081643244582, 归属分区 0, 准备进行变更操作
2026/04/08 10:37:15 [ERROR] 查询不到给定key[k], 尝试添加KV: k -> 6.5840
2026/04/08 10:37:15 [DEBUG] 已添加 k -> 6.5840, 版本索引为 0
2026/04/08 10:37:15 [DEBUG] 正在查询: k; 哈希值为 16323581081643244582, 归属分区 0
2026/04/08 10:37:15 [INFO] 查询到给定key[k]
2026/04/08 10:37:15 [DEBUG] 查询到 k -> 6.5840
kvsrv_test.go:30: Get value err ; expected 6.5840
--- FAIL: TestReliablePut (0.37s)
=== RUN TestPutConcurrentReliable

- 如图,
Copy并没有成功把字节拷贝过去……
因为copy拷贝的字节数量取决于目标数组和源数组的长度的最小值,在这里我把dest的长度设置为0,当然拷贝不过去
PUT方法实现不到位
┌──(root㉿kali)-[/develop/6.5840/src]
└─# make RUN="-run Reliable" kvsrv1
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
cd ..
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
=== RUN TestReliablePut
One client and reliable Put (reliable network)...
2026/04/08 10:41:16 [DEBUG] 正在查询: k; 哈希值为 8773152542757071180, 归属分区 0, 准备进行变更操作
2026/04/08 10:41:16 [ERROR] 查询不到给定key[k], 尝试添加KV: k -> 6.5840
2026/04/08 10:41:16 [DEBUG] 已添加 k -> 6.5840, 版本索引为 0
2026/04/08 10:41:16 [DEBUG] 正在查询: k; 哈希值为 8773152542757071180, 归属分区 0
2026/04/08 10:41:16 [INFO] 查询到给定key[k]
2026/04/08 10:41:16 [DEBUG] 查询到 k -> 6.5840
kvsrv_test.go:32: Get wrong version 0; expected 1
--- FAIL: TestReliablePut (0.21s)
PUT操作本身竟然也要对version index进行一次加一操作
【PUT方法并发可用性测试】一旦并发起来就会被检测出来出现锁竞争

客户端此时还没有实现指数退避算法,一遇到阻塞就会报错服务器繁忙或是找不到Key
分区这个数组本身是不加锁的,虽然只是一次指针覆盖,也仍然可能并发竞争到覆盖过程的中间态。解决办法就是把那行去掉即可
虽然这个测试样例确实能过:
... Passed -- time 1.3s #peers 1 #RPCs 297 #Ops 594也就是每一次RPC都需要两次操作
【信道不可用测试】PUT操作发生了多次
# 运行所有测试
make RUN="-run Reliable" kvsrv1
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run Reliable
cd ..
# 运行信道不可用测试
go build -race -o main/kvsrv1d main/kvsrv1d.go
cd kvsrv1 && go test -v -race -run "Unreliable"
cd ..2026/04/09 00:05:03 [INFO] 查询到给定key[k]
2026/04/09 00:05:03 [DEBUG] 查询到 k -> 0, 准备覆盖
2026/04/09 00:05:03 [DEBUG] 已覆盖为 k -> 0, 版本索引为 10
2026/04/09 00:05:03 [DEBUG] 正在查询: k; 哈希值为 4827175114863531959, 归属分区 0
2026/04/09 00:05:03 [INFO] 查询到给定key[k]
2026/04/09 00:05:03 [DEBUG] 查询到 k -> 0
2026/04/09 00:05:03 [DEBUG] 正在查询: k; 哈希值为 4827175114863531959, 归属分区 0, 准备进行变更操作
2026/04/09 00:05:03 [INFO] 查询到给定key[k]
2026/04/09 00:05:03 [DEBUG] 查询到 k -> 0, 准备覆盖
2026/04/09 00:05:03 [DEBUG] 已覆盖为 k -> 0, 版本索引为 11
2026/04/09 00:05:03 [DEBUG] 正在查询: k; 哈希值为 4827175114863531959, 归属分区 0
2026/04/09 00:05:03 [INFO] 查询到给定key[k]
2026/04/09 00:05:03 [DEBUG] 查询到 k -> 0
2026/04/09 00:05:03 [DEBUG] 正在查询: k; 哈希值为 4827175114863531959, 归属分区 0, 准备进行变更操作
2026/04/09 00:05:03 [INFO] 查询到给定key[k]
2026/04/09 00:05:03 [DEBUG] 查询到 k -> 0, 准备覆盖
2026/04/09 00:05:03 [DEBUG] 已覆盖为 k -> 0, 版本索引为 12
2026/04/09 00:05:03 [DEBUG] 正在查询: k; 哈希值为 4827175114863531959, 归属分区 0, 准备进行变更操作
2026/04/09 00:05:03 [ERROR] KV索引[11]与云端版本索引不匹配[12], KV添加失败
2026/04/09 00:05:03 [DEBUG] 正在查询: k; 哈希值为 4827175114863531959, 归属分区 0
2026/04/09 00:05:03 [INFO] 查询到给定key[k]
2026/04/09 00:05:03 [DEBUG] 查询到 k -> 0
2026/04/09 00:05:03 [DEBUG] 正在查询: k; 哈希值为 4827175114863531959, 归属分区 0, 准备进行变更操作
2026/04/09 00:05:03 [INFO] 查询到给定key[k]
2026/04/09 00:05:03 [DEBUG] 查询到 k -> 0, 准备覆盖
2026/04/09 00:05:03 [DEBUG] 已覆盖为 k -> 2, 版本索引为 13
kvsrv_test.go:131: Put shouldn't have happen more than once OK
--- FAIL: TestUnreliableNet (0.68s)
FAIL测试样例的请求重试代码:
for try := 0; try < NTRY; try++ {
for i := 0; true; i++ {
if err := ts.PutJson(ck, "k", i, rpc.Tversion(try), 0); err != rpc.ErrMaybe {
if i > 0 && err != rpc.ErrVersion {
t.Fatalf("Put shouldn't have happen more than once %v", err)
}
break
}
// Try put again; it should fail with ErrVersion
retried = true
}
v := 0
if ver := ts.GetJson(ck, "k", 0, &v); ver != rpc.Tversion(try+1) {
t.Fatalf("Wrong version %d expect %d", ver, try+1)
}
if v != 0 {
t.Fatalf("Wrong value %d expect %d", v, 0)
}
}GET方法的实现:
// client.go
func (ck *Clerk) Get(key string) (v string, i rpc.Tversion, err rpc.Err) {
var (
req = &rpc.GetArgs{Key: key}
resp = &rpc.GetReply{}
)
if ck.clnt.Call(ck.server, MethodGet, req, resp) {
return resp.Value, resp.Version, resp.Err
} else {
return "", 0, rpc.ErrMaybe
}
}
// cache/utils.go
func (storage *MapStorage) Get(key string) (value []byte, index uint64, err error) {
h, i := str2keyAndIndex(key, storage.option.PartitionSize)
log.Printf("[DEBUG] 正在查询: %v; 哈希值为 %v, 归属分区 %v", key, h, i)
// 定位分区
var (
partition = storage.Partitions[i]
v *Bucket
exists bool
)
partition.mu.RLock()
defer partition.mu.RUnlock()
if v, exists = partition.Buckets[h]; !exists {
log.Printf("[ERROR] 查询不到给定key[%v]", key)
return []byte{}, 0, ErrKeyNotFound
} else {
log.Printf("[INFO] 查询到给定key[%v]", key)
log.Printf("[DEBUG] 查询到 %v -> %v", key, string(v.Value))
value = make([]byte, len(v.Value))
copy(value, v.Value)
// value = append(value, v.Value...)
return value, v.Index, nil
}
}
func (storage *MapStorage) TryGet_(ctx context.Context, key string) (v []byte, i uint64, err error) {
wait, cancel := context.WithTimeout(ctx, storage.option.WaitDuration)
defer cancel()
var done = make(chan struct{})
go func() {
v, i, err = storage.Get(key)
done <- struct{}{}
close(done)
}()
select {
case <-wait.Done():
return nil, 0, ErrServerBusy
case <-done:
return v, i, err
}
}
func (storage *MapStorage) TryGet(key string) (v []byte, i uint64, err error) {
return storage.TryGet_(context.Background(), key)
}猜测为实现GET方法时抛出的错误ErrServerBusy导致的,而客户端遇到这个错误时不假思索地抛出去了,因为既不是ErrVersion也不是ErrMaybe,所以爆炸了
客户端没有返回过ErrMaybe
加了一个随机重试之后……

到底要我怎么样???
抛出了也不是,不抛出也不是


真给你连带ErrMaybe一起重试了又不乐意了

重试也不行,不重试也不行
最后是改成了PUT不重试,GET重试1000次
PUT操作丢失

2026/04/09 02:58:42 [DEBUG][Server Side] 接收到GET请求
2026/04/09 02:58:42 [DEBUG] 正在查询: k; 哈希值为 16782530992807324830, 归属分区 0
2026/04/09 02:58:42 [INFO] 查询到给定key[k]
2026/04/09 02:58:42 [DEBUG] 查询到 k -> 0
2026/04/09 02:58:42 [DEBUG][Server Side] 已处理GET请求
2026/04/09 02:58:42 [DEBUG][Server Side] 接收到PUT请求
2026/04/09 02:58:42 [DEBUG] 正在查询: k; 哈希值为 16782530992807324830, 归属分区 0, 准备进行变更操作
2026/04/09 02:58:42 [INFO] 查询到给定key[k]
2026/04/09 02:58:42 [DEBUG] 查询到 k -> 0, 准备覆盖
2026/04/09 02:58:42 [DEBUG] 已覆盖为 k -> 0, 版本索引为 15
2026/04/09 02:58:42 [DEBUG][Server Side] 已处理PUT请求
2026/04/09 02:58:42 [WARNING] 测试样例故意抛出 ErrMaybe, 这不是服务端可以抛出的异常
2026/04/09 02:58:42 [ERROR] 请求失败: ErrMaybe, 剩余重试次数: 0 次
2026/04/09 02:58:42 [DEBUG][Server Side] 接收到GET请求 // 注意这里
2026/04/09 02:58:42 [DEBUG] 正在查询: k; 哈希值为 16782530992807324830, 归属分区 0
2026/04/09 02:58:42 [INFO] 查询到给定key[k]
2026/04/09 02:58:42 [DEBUG] 查询到 k -> 0
2026/04/09 02:58:42 [DEBUG][Server Side] 已处理GET请求
2026/04/09 02:58:42 [WARNING] 测试样例故意抛出 ErrMaybe, 这不是服务端可以抛出的异常
2026/04/09 02:58:42 [ERROR] 请求失败: ErrMaybe, 剩余重试次数: 0 次
2026/04/09 02:58:42 [DEBUG][Server Side] 接收到GET请求 // 和这里
2026/04/09 02:58:42 [DEBUG] 正在查询: k; 哈希值为 16782530992807324830, 归属分区 0
2026/04/09 02:58:42 [INFO] 查询到给定key[k]
2026/04/09 02:58:42 [DEBUG] 查询到 k -> 0
2026/04/09 02:58:42 [DEBUG][Server Side] 已处理GET请求
kvsrv_test.go:144: Wrong version 15 expect 16
--- FAIL: TestUnreliableNet (1.08s)- 最后两次
GET请求之间没有PUT请求,就这怎么好意思说版本号递增的呢? - 无奈还是给
PUT请求补了重试逻辑,但这就又回到 客户端不返回ErrMaybe那个问题上了

我真是日了狗了
不重试嫌我太脆弱,重试嫌我报错,MIT真是有了
???


有毛病吧?
真给跑满了又不乐意了

最后面是硬改测试样例,把never returned ErrMaybe删掉了
事能办成就别管太多了,至于怎么办成的你别问
锁测试中多客户端信道不可用测试样例发生锁竞争

前三个测试都能通过,最后一个测试在第85行发生了panic(t.Fatalf会在打印语句后中断程序,但由于协程还在跑,所以程序终止之后日志又输出了一段时间)
怀疑是因为代码里加了防御性超时解锁逻辑,导致多客户端竞争而部分客户端断线的情况下,部分锁被自动覆盖了

进一步测试发现是因为Release给PUT传递的客户端标识符是空值,被判定为了无效更改;而校验申请者和持有者的逻辑实际上一直由超时解锁代劳


改为在传参中同时包括客户端名称和锁变更模式后,仍然会在第四个测试样例卡住,仍然会发生锁竞争的情况
后面发现是鉴权不够完善
左右为难
2026/04/16 05:54:15 [ERROR] LockTest客户端[26f35d20]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:15 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[60c901f1]不匹配
2026/04/16 05:54:15 [ERROR] LockTest客户端[60c901f1]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:16 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[b8b12739]不匹配
2026/04/16 05:54:16 [ERROR] LockTest客户端[b8b12739]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:16 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[8d2d4c63]不匹配
2026/04/16 05:54:16 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[bed48b62]不匹配
2026/04/16 05:54:16 [ERROR] LockTest客户端[8d2d4c63]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:16 [ERROR] LockTest客户端[bed48b62]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:16 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[a7fbfcfc]不匹配
2026/04/16 05:54:16 [ERROR] 当前锁的持有者为0abb5707, 与申请者[6a397bea]不匹配
2026/04/16 05:54:16 [ERROR] LockTest客户端[a7fbfcfc]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:16 [ERROR] LockTest客户端[6a397bea]无法获取锁[_lock:UleHWrNYUycH]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:16 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[2b79c790]不匹配
2026/04/16 05:54:16 [ERROR] LockTest客户端[2b79c790]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:16 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[f6e92474]不匹配
2026/04/16 05:54:16 [ERROR] LockTest客户端[f6e92474]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:16 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[26f35d20]不匹配
2026/04/16 05:54:16 [ERROR] LockTest客户端[26f35d20]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:16 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[60c901f1]不匹配
2026/04/16 05:54:16 [ERROR] LockTest客户端[60c901f1]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:17 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[b8b12739]不匹配
2026/04/16 05:54:17 [ERROR] LockTest客户端[b8b12739]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:17 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[8d2d4c63]不匹配
2026/04/16 05:54:17 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[bed48b62]不匹配
2026/04/16 05:54:17 [ERROR] LockTest客户端[8d2d4c63]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:17 [ERROR] LockTest客户端[bed48b62]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:17 [ERROR] 当前锁的持有者为0abb5707, 与申请者[6a397bea]不匹配
2026/04/16 05:54:17 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[a7fbfcfc]不匹配
2026/04/16 05:54:17 [ERROR] LockTest客户端[a7fbfcfc]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:17 [ERROR] LockTest客户端[6a397bea]无法获取锁[_lock:UleHWrNYUycH]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:17 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[2b79c790]不匹配
2026/04/16 05:54:17 [ERROR] LockTest客户端[2b79c790]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:17 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[f6e92474]不匹配
2026/04/16 05:54:17 [ERROR] LockTest客户端[f6e92474]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:17 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[26f35d20]不匹配
2026/04/16 05:54:17 [ERROR] LockTest客户端[26f35d20]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
2026/04/16 05:54:17 [ERROR] 当前锁的持有者为e6eb0f67, 与申请者[60c901f1]不匹配
2026/04/16 05:54:17 [ERROR] LockTest客户端[60c901f1]无法获取锁[_lock:IsNyDAcpDjCx]: ErrAcquire, 等待1000毫秒后重试
^Cexit status 1不加超时解锁就会这样,加了就会在ManyClientsReliableNet里面挂掉



这怎么都这样了还能出现锁竞争
检查发现是Lab 2A的幂等不完备导致的:
if v.Index != index {
if index < v.Index {
// 这里原本的版本是直接返回nil, 以实现操作幂等
// 但在分布式锁的情景下, 可能会导致客户端重试时出现竞争锁的情况
// 所以要检查锁信息, 并且要检查当前请求是否为锁变更请求
if v.info.Label == LockInEffect && mode != "" && v.info.LastAcquireBy == clientID {
// 操作幂等
return nil
}
}
return ErrIndexMismatch当某个客户端版本低于服务端的版本时,旧版服务端会直接返回nil,让客户端以为自己拿到了锁。改之前一次测试能出二十余次Fatal,改了之后锐减到 1~2次
谜之锁竞争
2026/04/16 06:54:50 [INFO] LockTest客户端[55aca4c9:Acquire]成功获取锁
2026/04/16 06:54:50 6: acquired lock <- 6号客户端拿到了锁
2026/04/16 06:54:50 [DEBUG][Server Side] 客户端[80594922]申请Acquire指定锁[_lock:wbTMsCHWRFto]
2026/04/16 06:54:50 5: release lock <- 随后5号客户端解开了锁???
2026/04/16 06:54:50 [INFO][Server Side] 客户端[80594922]申请Acquire指定锁[_lock:wbTMsCHWRFto], 结果为: 客户端KV版本与服务端不匹配
2026/04/16 06:54:50 [ERROR] LockTest客户端[80594922]无法获取锁[_lock:wbTMsCHWRFto]: ErrVersion, 等待1000毫秒后重试
2026/04/16 06:54:50 [DEBUG][Server Side] 客户端[77849a00]申请Release指定锁[_lock:wbTMsCHWRFto]
2026/04/16 06:54:50 [INFO][Server Side] 客户端[77849a00]申请Release指定锁[_lock:wbTMsCHWRFto], 结果为: <nil>
2026/04/16 06:54:50 [INFO] LockTest客户端[77849a00:Release]成功释放锁
2026/04/16 06:54:50 [DEBUG][Server Side] 客户端[77849a00]申请Acquire指定锁[_lock:wbTMsCHWRFto]
2026/04/16 06:54:50 [INFO][Server Side] 客户端[77849a00]申请Acquire指定锁[_lock:wbTMsCHWRFto], 结果为: <nil>
2026/04/16 06:54:50 [INFO] LockTest客户端[77849a00:Acquire]成功获取锁
2026/04/16 06:54:50 5: acquired lock
lock_test.go:86: [FATAL-ERROR][2026-04-16 06:54:50] 5: two clients acquired lock 6
2026/04/16 06:54:50 6: release lock
2026/04/16 06:54:50 [DEBUG][Server Side] 客户端[55aca4c9]申请Release指定锁[_lock:wbTMsCHWRFto]
2026/04/16 06:54:50 [INFO][Server Side] 客户端[55aca4c9]申请Release指定锁[_lock:wbTMsCHWRFto], 结果为: <nil>“得了MVP!!!”

b534d5b1以一己之力卡十个客户端,b534d5b1是MVP!
后面发现是Release方法绑到了_acquire这个Handler上
锁覆盖的问题复发 / Race Between Get and Put
- 此时已经重写Acquire和Release,使之完全依赖GET和PUT接口实现,但服务端没有实现事务功能,无法保证下游接口是原子性的
- 在两次GET和PUT之间存在状态竞争,会导致锁状态被两个同样有效的请求所覆盖
放!!!
2026/04/16 11:02:45 [ERROR][Acquire] [438fd91c]获取锁[lock:QjtRSHWeqGcT]失败, 等待1000ms后继续尝试
2026/04/16 11:02:45 [STATE MACHINE] cda38306: 客户端空闲中 -> 客户端已无法恢复
2026/04/16 11:02:45 [ERROR] [cda38306]客户端报告锁[lock:QjtRSHWeqGcT]已被释放!!!
2026/04/16 11:02:45 [PANIC RECOVERED]: [ERROR] [cda38306]客户端报告锁[lock:QjtRSHWeqGcT]已被释放!!!
2026/04/16 11:02:45 [ERROR][Acquire] [cda38306]获取锁[lock:QjtRSHWeqGcT]失败, 等待1000ms后继续尝试
2026/04/16 11:02:45 [DEBUG][Release] [43940df4]没有得到锁, 请不要尝试释放任何锁
2026/04/16 11:02:45 [DEBUG][Release] [78b85793]没有得到锁, 请不要尝试释放任何锁
2026/04/16 11:02:45 [DEBUG][Release] [555801d9]没有得到锁, 请不要尝试释放任何锁
2026/04/16 11:02:45 [STATE MACHINE] 78b85793: 客户端空闲中 -> 客户端已无法恢复
2026/04/16 11:02:45 [ERROR] [78b85793]客户端报告锁[lock:QjtRSHWeqGcT]已被释放!!!
2026/04/16 11:02:45 [PANIC RECOVERED]: [ERROR] [78b85793]客户端报告锁[lock:QjtRSHWeqGcT]已被释放!!!
2026/04/16 11:02:45 [ERROR][Acquire] [78b85793]获取锁[lock:QjtRSHWeqGcT]失败, 等待1000ms后继续尝试
2026/04/16 11:02:45 [STATE MACHINE] 555801d9: 客户端空闲中 -> 客户端已无法恢复
2026/04/16 11:02:45 [ERROR] [555801d9]客户端报告锁[lock:QjtRSHWeqGcT]已被释放!!!
2026/04/16 11:02:45 [PANIC RECOVERED]: [ERROR] [555801d9]客户端报告锁[lock:QjtRSHWeqGcT]已被释放!!!
2026/04/16 11:02:45 [ERROR][Acquire] [555801d9]获取锁[lock:QjtRSHWeqGcT]失败, 等待1000ms后继续尝试
2026/04/16 11:02:45 [STATE MACHINE] 43940df4: 客户端空闲中 -> 客户端已无法恢复
2026/04/16 11:02:45 [ERROR] [43940df4]客户端报告锁[lock:QjtRSHWeqGcT]已被释放!!!
2026/04/16 11:02:45 [PANIC RECOVERED]: [ERROR] [43940df4]客户端报告锁[lock:QjtRSHWeqGcT]已被释放!!!
2026/04/16 11:02:45 [ERROR][Acquire] [43940df4]获取锁[lock:QjtRSHWeqGcT]失败, 等待1000ms后继续尝试
Lab 2到此为止
由于架构问题,接下来的测验如果要完成,都将涉及结构重写,吃力不讨好
花絮
在缓存路径里找EXE的幽默测试样例


- 猜测是因为
kvsrv1d不带后缀名,Windows上构建的Go程序默认会到缓存路径里找文件,即便用的是带路径分隔符的绝对路径 - 给
prod打个补丁,检查runtime.GOOS,看情况补个.exe后缀就解决了
疑似Pure Go 网络栈


不过底层还是unix文件套接字,还是起Linux虚拟机