Mit分布式系统课程-Lab2-PartB
前两天删了在github上的6.824, 因为课程里说了不要把源码放在公共的地方, 之前没删一直心有歉意, 还好进度没这么快, 应该对别人也不会造成影响.
言归正传, 这星期空余时间一直在看Lab2的Part B, 今天因为身体不适, 请假一天在家, 开始实现思路, 并记录一下自己遇到的一些大小问题.
目标
实现基于viewservice(Lab1实现的视图服务器, 用来记录主副服务器的地址)的键值服务器, 分别为primary(主服务器),Backup(备份服务器), 客户端通过viewservice获得primary的地址, 发送键值的存取请求, primary在响应请求的同时, 将数据备份到backup服务器. 实现完成后, 通过所有的unit test.
架构
- viewservice
- 存储并提供primary以及backup的信息
- 接受primary, backup的定时ping请求, 更新或保持两个服务器的位置
- 提供查询当前服务器状态
- primary
- 接受client的请求
- 定期向viewservice报备自己的状态
- 在backup服务器更换时负责将自身全部数据同步过去
- backup
- 作为primary的备份存储
- 定期向viewservice报备自己的状态
- 在primary挂了之后, 提升为primary, 负责client的请求
- client
- 向primary发送数据存储,查询的请求
- 若primary返回”错误的服务器”时, 询问viewservice, 更新primary
遇到的挑战
- 实现时有些golang的特性不是特别了解, 一边打开golang的官方文档, 一边poc.
- 决定在什么时候需要容错? 是否可恢复? 在反复重试时, 是否要定最大重试次数, 以及失败的处理.
单元测试
当我运行go test来验证逻辑的时候, 实在不得不赞叹老师们详细的测试用例, 有几个case让我挠头.
- TestAtMostOnce
模拟服务器返回报文丢失的情况, 利用puthash-get验证, puthash是put操作的一种变体, 它要存储的值是先前的值和当前值的hash结果, 所以这个地方引入了存储状态, 我通过每次请求要求客户端发送一个unique id来记录此次操作, 尽管报文丢失, 客户端重试时还是能用这个id找回上次通信的内容. - TestConcurrentSame
验证primary服务器只有在backup也处理成功的情况才算成功. - TestRepeatedCrash
模拟存储服务器间断崩溃的情况, 就是要保证客户端的每个请求都有重试的可能, primary和backup的通信过程中, backup随时可能崩溃, 这个测试使用put-get验证. - TestRepeatedCrashUnreliable
模拟存储服务器间断崩溃的同时, 网络也不稳定, 服务器的返回报文会丢失. 这个case的难点在它是使用puthash-get来验证的, 就是说, 你要在服务器不时崩溃的情况下保证以前的值不仅在primary服务器上存在, 也要在backup服务器上保存, 以免primary下一秒挂了. - TestPartition1
模拟primary已经过期的情况下, 向他发送的请求, 能被它拒绝.
POC
backup服务器更换时, primary需要同步当前数据库所有数据到backup, 我不想用foreach来每条数据进行同步, 所以想既然rpc能传递这么多类型的参数, 那直接将map传递过去不就不用那么麻烦了?所以进行了一次poc.
server.go
1 | import "net" |
client.go
1 | package main |
开两个terminal, 在$GOPATH目录先运行server.go, 再运行client.go, 最后server.go运行后的输出, 看来是可以的
1 | $ go run server.go |
在我把这页的hint也看完的时候, 发现里面提到这个问题, 建议直接把这个map作为参数传递.