Practice datacenter failover in production

Practice datacenter failover in production

Distributed system is like human body, it will have issues and break. There’s a theory that we feed it with issues deliberately and constantly, the body will be more and more stable and robust. It’s the same to system, put some issues to datacenters and let them failover automatically.

Read more

分布式系统频次限制实现

频次限制(rate limiting)是Web系统比较常见的功能,防止用户频繁访问接口,导致系统负载增加而影响服务的质量。这里来介绍在分布式系统中实现一个共享的频次限制服务,且保证接口低时延高访问量。

Read more

Mit分布式系统课程-Lab2-PartB

前两天删了在github上的6.824, 因为课程里说了不要把源码放在公共的地方, 之前没删一直心有歉意, 还好进度没这么快, 应该对别人也不会造成影响.

言归正传, 这星期空余时间一直在看Lab2的Part B, 今天因为身体不适, 请假一天在家, 开始实现思路, 并记录一下自己遇到的一些大小问题.

目标

实现基于viewservice(Lab1实现的视图服务器, 用来记录主副服务器的地址)的键值服务器, 分别为primary(主服务器),Backup(备份服务器), 客户端通过viewservice获得primary的地址, 发送键值的存取请求, primary在响应请求的同时, 将数据备份到backup服务器. 实现完成后, 通过所有的unit test.

架构

2个kv服务器, viewservice以及客户端

  1. viewservice
    • 存储并提供primary以及backup的信息
    • 接受primary, backup的定时ping请求, 更新或保持两个服务器的位置
    • 提供查询当前服务器状态
  2. primary
    • 接受client的请求
    • 定期向viewservice报备自己的状态
    • 在backup服务器更换时负责将自身全部数据同步过去
  3. backup
    • 作为primary的备份存储
    • 定期向viewservice报备自己的状态
    • 在primary挂了之后, 提升为primary, 负责client的请求
  4. client
    • 向primary发送数据存储,查询的请求
    • 若primary返回”错误的服务器”时, 询问viewservice, 更新primary

遇到的挑战

  1. 实现时有些golang的特性不是特别了解, 一边打开golang的官方文档, 一边poc.
  2. 决定在什么时候需要容错? 是否可恢复? 在反复重试时, 是否要定最大重试次数, 以及失败的处理.

单元测试

当我运行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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import "net"
import "net/rpc"
import "os"
import "fmt"

type Server struct {
db map[string]string
}

func (server *Server) Sync(database map[string]string, reply *int) error {
fmt.Println("Start syncing...")
server.db = database
for k, v := range server.db {
fmt.Println(k, " => ", v)
}
*reply = 1
fmt.Println("Finish syncing...")
return nil
}

func main() {
server := new(Server)
server.db = make(map[string]string)
rpcs := rpc.NewServer()
rpcs.Register(server)
me := "/var/tmp/test1122"
os.Remove(me)
l, e := net.Listen("unix", me)
if e != nil {
fmt.Println("listen error")
}

for true {
conn, e := l.Accept()
if e != nil {
fmt.Println("accept error")
conn.Close()
return
}
go func() {
rpcs.ServeConn(conn)
}()
}
}
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import "net/rpc"
import "fmt"

func main() {
me := "/var/tmp/test1122"
c, e := rpc.Dial("unix", me)
if e != nil {
fmt.Println("dial error")
return
}
defer c.Close()

db := make(map[string]string)
db["name"] = "srg"
db["age"] = "25"
reply := 1
e = c.Call("Server.Sync", db, &reply)
if e != nil {
fmt.Println("sync error")
return
}
}

开两个terminal, 在$GOPATH目录先运行server.go, 再运行client.go, 最后server.go运行后的输出, 看来是可以的

1
2
3
4
5
$ go run server.go                                                             
Start syncing...
name => srg
age => 25
Finish syncing...

在我把这页的hint也看完的时候, 发现里面提到这个问题, 建议直接把这个map作为参数传递.

最后贴上过关纪念

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×