├── .github ├── dependabot.yml └── workflows │ ├── golangci-lint.yml │ └── test.yml ├── .gitignore ├── .gitpod.yml ├── .travis.yml ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── install-protoc.sh ├── paxoskv ├── example_set_get_test.go ├── impl.go ├── impl_test.go ├── paxos_slides_case_test.go └── paxoskv.pb.go └── proto └── paxoskv.proto /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | branches-ignore: 5 | - '**' 6 | # tags: 7 | # - v* 8 | # branches: 9 | # - '*' 10 | # pull_request: 11 | jobs: 12 | golangci: 13 | name: lint 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: golangci-lint 19 | uses: golangci/golangci-lint-action@v2 20 | with: 21 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 22 | version: v1.29 23 | 24 | # Optional: working directory, useful for monorepos 25 | # working-directory: somedir 26 | 27 | # Optional: golangci-lint command line arguments. 28 | # args: --issues-exit-code=0 29 | 30 | # Optional: show only new issues if it's a pull request. The default value is `false`. 31 | # only-new-issues: true 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | test: 8 | strategy: 9 | matrix: 10 | go-version: 11 | - 1.14.x 12 | - 1.15.x 13 | - 1.16.x 14 | - 1.17.x 15 | os: [ubuntu-latest, macos-latest, windows-latest] 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - name: Install Go 19 | uses: actions/setup-go@v3 20 | with: 21 | go-version: ${{ matrix.go-version }} 22 | 23 | - name: checkout 24 | uses: actions/checkout@v2 25 | 26 | - name: cache 27 | uses: actions/cache@v2 28 | with: 29 | path: ~/go/pkg/mod 30 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 31 | restore-keys: | 32 | ${{ runner.os }}-go- 33 | 34 | - name: test 35 | run: go test -v ./... 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Mac hidden file 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: gitpod/workspace-full 2 | 3 | tasks: 4 | - init: go get && go build ./... && go test ./... && make 5 | command: go run 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.14.x 5 | - 1.15.x 6 | - 1.16.x 7 | - 1.17.x 8 | - tip 9 | 10 | jobs: 11 | # go tip does not pass: 12 | # paxoskv/impl.go:12:2: no required module provides package golang.org/x/net/context; try 'go mod tidy' to add it 13 | allow_failures: 14 | - go: tip 15 | install: 16 | - ./install-protoc.sh 17 | - go get github.com/golang/protobuf/protoc-gen-go 18 | script: 19 | - make gen 20 | - go test ./... 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | gen: gen-go 3 | 4 | gen-go: 5 | protoc --proto_path=proto \ 6 | --go_out=plugins=grpc:paxoskv \ 7 | --go_opt=paths=source_relative \ 8 | paxoskv.proto 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # paxoskv: a Naive and Basic paxos kv storage 2 | 3 | ![naive](https://github.com/openacid/paxoskv/workflows/test/badge.svg?branch=naive) 4 | [![Build Status](https://travis-ci.com/openacid/paxoskv.svg?branch=naive)](https://travis-ci.com/openacid/paxoskv) 5 | 6 | 这个repo 目前仅是用于学习的实例代码. 7 | 8 | 这是一个基于paxos, 只有200行代码的kv存储系统的简单实现, 以最简洁的形式展示paxos如何运行, 作为 [可靠分布式系统-paxos的直观解释][] 这篇教程中的示例. 9 | [200行代码实现基于paxos的kv存储][] 是对本代码讲解的教程. 10 | 11 | 因为有不少网友跟我问起这篇教程中的实现问题, 例如怎么把只能确定一个值的paxos应用到实际场景中. 12 | 既然**Talk is cheap**, 那么就**Show me the code**, 把教程中描述的内容直接用代码实现出来, 希望能覆盖到教程中的每个细节. 帮助大家理解paxos的运行机制. 13 | 14 | NB-paxoskv 通过classic paxos建立一个简单的kv存储, 15 | 这个版本只支持指定key-version的写入和读取: 16 | 17 | - 写入操作通过一次2轮的paxos实现. 18 | 19 | - 读取操作也通过一次1轮或2轮的paxos实现. 20 | 21 | - 虽然每个key支持更新(通过多个ver), 22 | 但在这个版本的代码中只能通过指定ver的方式写入, 23 | 目前还不支持把对key的更新自动作为下一个ver来写入(不似生产环境kv存储的实现). 24 | 25 | - 没有以状态机的方式实现 WAL and compaction的存储, 它直接把paxos instance对应到key的每个版本上. 26 | 27 | # 名词 28 | 29 | 在paxos相关的paper, [可靠分布式系统-paxos的直观解释][], 30 | 以及这个repo中代码涉及到的各种名词, 下面列出的都是等价的: 31 | 32 | ``` 33 | rnd == bal == BallotNum ~= Ballot 34 | quorum == majority == 多数派 35 | voted value == accepted value // by an acceptor 36 | ``` 37 | 38 | # Usage 39 | 40 | Requirements: `go >= 1.14`. 41 | 42 | 跑测试: `go test ./...`. 43 | 44 | 重新build proto文件(如果宁想要修改下玩玩的话): `make gen`. 45 | 46 | 数据结构使用protobuf 定义; RPC使用grpc实现; 47 | 48 | 如想了解最新的go grpc的环境部署,请看[go-grpc文档](https://grpc.io/docs/languages/go/quickstart/) 49 | 50 | 51 | # 目录结构 52 | 53 | - `proto/paxoskv.proto`: 定义paxos相关的数据结构. 54 | 55 | - `paxoskv/`: 56 | 57 | - `impl.go`: 206行代码实现的paxos协议: 58 | - 实现paxos Acceptor的`Prepare()`和`Accept()`这两个request handler; 59 | - 实现Proposer的功能: 执行`Phase1()`和`Phase2()`, 60 | - 以及完整运行一次paxos的`RunPaxos()`方法; 61 | - 实现一个kv纯内存的存储, 每个key有多个version, 每个version对应一个paxos instance; 62 | - 以及启动n个Acceptor的grpc服务函数 63 | 64 | - `paxos_slides_case_test.go`: 按照 [可靠分布式系统-paxos的直观解释][] 给出的两个例子([slide-32][]和[slide-33][]), 调用paxos接口来模拟这2个场景中的paxos运行. 65 | 66 | - `example_set_get_test.go`: 使用paxos提供的接口实现指定key和ver的写入和读取. 67 | 68 | # Question 69 | 70 | 如果有任何问题, 欢迎提[issue] :DDD. 71 | 72 | 73 | [issue]: https://github.com/openacid/paxoskv/issues/new/choose 74 | [可靠分布式系统-paxos的直观解释]: https://blog.openacid.com/algo/paxos/ 75 | [200行代码实现基于paxos的kv存储]: https://blog.openacid.com/algo/paxoskv/ 76 | [slide-32]: https://blog.openacid.com/algo/paxos/#slide-32 77 | [slide-33]: https://blog.openacid.com/algo/paxos/#slide-33 78 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/openacid/paxoskv 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/golang/protobuf v1.5.2 7 | github.com/kr/pretty v0.3.1 8 | github.com/stretchr/testify v1.8.1 9 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 10 | google.golang.org/grpc v1.50.1 11 | google.golang.org/protobuf v1.28.1 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/kr/text v0.2.0 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | github.com/rogpeppe/go-internal v1.9.0 // indirect 19 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect 20 | golang.org/x/text v0.3.3 // indirect 21 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 22 | gopkg.in/yaml.v3 v3.0.1 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 5 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 6 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 8 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 9 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 10 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 11 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 12 | github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 13 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 14 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 19 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 20 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 21 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 22 | github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= 23 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 24 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 25 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 26 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 27 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 28 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 29 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 30 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 31 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 32 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 33 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 34 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 35 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 36 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 37 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 38 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 39 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 40 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 41 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 42 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 43 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 44 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 45 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 46 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 47 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 48 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 49 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 50 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 51 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 52 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 53 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 54 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 55 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 56 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 57 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 58 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 59 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 60 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 61 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 62 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 63 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 64 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 65 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 66 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 67 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 68 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 69 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 70 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 71 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 72 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 73 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 74 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 75 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 76 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 77 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 78 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 79 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 80 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 81 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 82 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 83 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 84 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 85 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 86 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw= 87 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 88 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 89 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 90 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 91 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 92 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 93 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 94 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 95 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 96 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 97 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 98 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 99 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= 100 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 101 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 102 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 103 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 104 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 105 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 106 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 107 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 108 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 109 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 110 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 111 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 112 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 113 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 114 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 115 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 116 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 117 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= 118 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 119 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 120 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 121 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 122 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 123 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 124 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 125 | google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= 126 | google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= 127 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 128 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 129 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 130 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 131 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 132 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 133 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 134 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 135 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 136 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 137 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 138 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 139 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 140 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 141 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 142 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 143 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 144 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 145 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 146 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 147 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 148 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 149 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 150 | -------------------------------------------------------------------------------- /install-protoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PROTOBUF_VERSION=3.10.0 4 | PROTOC_FILENAME=protoc-${PROTOBUF_VERSION}-linux-x86_64.zip 5 | 6 | ( 7 | 8 | cd /home/travis 9 | 10 | wget https://github.com/google/protobuf/releases/download/v$PROTOBUF_VERSION/$PROTOC_FILENAME 11 | unzip $PROTOC_FILENAME 12 | bin/protoc --version 13 | 14 | ) 15 | -------------------------------------------------------------------------------- /paxoskv/example_set_get_test.go: -------------------------------------------------------------------------------- 1 | package paxoskv 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func Example_setAndGetByKeyVer() { 8 | 9 | // In this example it set or get a key_ver by running a paxos instance. 10 | 11 | acceptorIds := []int64{0, 1, 2} 12 | 13 | servers := ServeAcceptors(acceptorIds) 14 | defer func() { 15 | for _, s := range servers { 16 | s.Stop() 17 | } 18 | }() 19 | 20 | // set foo₀ = 5 21 | { 22 | prop := Proposer{ 23 | Id: &PaxosInstanceId{ 24 | Key: "foo", 25 | Ver: 0, 26 | }, 27 | Bal: &BallotNum{N: 0, ProposerId: 2}, 28 | } 29 | v := prop.RunPaxos(acceptorIds, &Value{Vi64: 5}) 30 | fmt.Printf("written: %v;\n", v.Vi64) 31 | } 32 | 33 | // get foo₀ 34 | { 35 | prop := Proposer{ 36 | Id: &PaxosInstanceId{ 37 | Key: "foo", 38 | Ver: 0, 39 | }, 40 | Bal: &BallotNum{N: 0, ProposerId: 2}, 41 | } 42 | v := prop.RunPaxos(acceptorIds, nil) 43 | fmt.Printf("read: %v;\n", v.Vi64) 44 | } 45 | 46 | // set foo₁ = 6 47 | { 48 | prop := Proposer{ 49 | Id: &PaxosInstanceId{ 50 | Key: "foo", 51 | Ver: 1, 52 | }, 53 | Bal: &BallotNum{N: 0, ProposerId: 2}, 54 | } 55 | v := prop.RunPaxos(acceptorIds, &Value{Vi64: 6}) 56 | fmt.Printf("written: %v;\n", v.Vi64) 57 | } 58 | 59 | // get foo₁ 60 | { 61 | prop := Proposer{ 62 | Id: &PaxosInstanceId{ 63 | Key: "foo", 64 | Ver: 1, 65 | }, 66 | Bal: &BallotNum{N: 0, ProposerId: 2}, 67 | } 68 | v := prop.RunPaxos(acceptorIds, nil) 69 | fmt.Printf("read: %v;\n", v.Vi64) 70 | } 71 | 72 | // Output: 73 | // written: 5; 74 | // read: 5; 75 | // written: 6; 76 | // read: 6; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /paxoskv/impl.go: -------------------------------------------------------------------------------- 1 | package paxoskv 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net" 8 | "sync" 9 | "time" 10 | 11 | "github.com/kr/pretty" 12 | "golang.org/x/net/context" 13 | "google.golang.org/grpc" 14 | "google.golang.org/grpc/reflection" 15 | ) 16 | 17 | var ( 18 | NotEnoughQuorum = errors.New("not enough quorum") 19 | AcceptorBasePort = int64(3333) 20 | ) 21 | 22 | // GE compare two ballot number a, b and return whether a >= b in a bool 23 | func (a *BallotNum) GE(b *BallotNum) bool { 24 | if a.N > b.N { 25 | return true 26 | } 27 | if a.N < b.N { 28 | return false 29 | } 30 | return a.ProposerId >= b.ProposerId 31 | } 32 | 33 | // RunPaxos execute the paxos phase-1 and phase-2 to establish a value. 34 | // `val` is the value caller wants to propose. 35 | // It returns the established value, which may be a voted value that is not 36 | // `val`. 37 | // 38 | // If `val` is not nil, it writes it into the specified version of a record. 39 | // The record key and the version is specified by p.PaxosInstanceId, since every 40 | // update of a record(every version) is impl by a paxos instance. 41 | // 42 | // If `val` is nil, it acts as a reading operation: 43 | // it reads the specified version of a record by running a paxos without propose 44 | // any value: This func will finish paxos phase-2 to make it safe if a voted 45 | // value is found, otherwise, it just returns nil without running phase-2. 46 | func (p *Proposer) RunPaxos(acceptorIds []int64, val *Value) *Value { 47 | 48 | quorum := len(acceptorIds)/2 + 1 49 | 50 | for { 51 | p.Val = nil 52 | 53 | maxVotedVal, higherBal, err := p.Phase1(acceptorIds, quorum) 54 | if err != nil { 55 | pretty.Logf("Proposer: fail to run phase-1: highest ballot: %v, increment ballot and retry", higherBal) 56 | p.Bal.N = higherBal.N + 1 57 | continue 58 | } 59 | 60 | if maxVotedVal == nil { 61 | pretty.Logf("Proposer: no voted value seen, propose my value: %v", val) 62 | } else { 63 | val = maxVotedVal 64 | } 65 | 66 | if val == nil { 67 | pretty.Logf("Proposer: no value to propose in phase-2, quit") 68 | return nil 69 | } 70 | 71 | p.Val = val 72 | pretty.Logf("Proposer: proposer chose value to propose: %s", p.Val) 73 | 74 | higherBal, err = p.Phase2(acceptorIds, quorum) 75 | if err != nil { 76 | pretty.Logf("Proposer: fail to run phase-2: highest ballot: %v, increment ballot and retry", higherBal) 77 | p.Bal.N = higherBal.N + 1 78 | continue 79 | } 80 | 81 | pretty.Logf("Proposer: value is voted by a quorum and has been safe: %v", maxVotedVal) 82 | return p.Val 83 | } 84 | } 85 | 86 | // Phase1 run paxos phase-1 on the specified acceptorIds. 87 | // If a higher ballot number is seen and phase-1 failed to constitute a quorum, 88 | // one of the higher ballot number and a NotEnoughQuorum is returned. 89 | func (p *Proposer) Phase1(acceptorIds []int64, quorum int) (*Value, *BallotNum, error) { 90 | 91 | replies := p.rpcToAll(acceptorIds, "Prepare") 92 | 93 | ok := 0 94 | higherBal := *p.Bal 95 | maxVoted := &Acceptor{VBal: &BallotNum{}} 96 | 97 | for _, r := range replies { 98 | 99 | pretty.Logf("Proposer: handling Prepare reply: %s", r) 100 | if !p.Bal.GE(r.LastBal) { 101 | if r.LastBal.GE(&higherBal) { 102 | higherBal = *r.LastBal 103 | } 104 | continue 105 | } 106 | 107 | // find the voted value with highest vbal 108 | if r.VBal.GE(maxVoted.VBal) { 109 | maxVoted = r 110 | } 111 | 112 | ok += 1 113 | if ok == quorum { 114 | return maxVoted.Val, nil, nil 115 | } 116 | } 117 | 118 | return nil, &higherBal, NotEnoughQuorum 119 | 120 | } 121 | 122 | // Phase2 run paxos phase-2 on the specified acceptorIds. 123 | // If a higher ballot number is seen and phase-2 failed to constitute a quorum, 124 | // one of the higher ballot number and a NotEnoughQuorum is returned. 125 | func (p *Proposer) Phase2(acceptorIds []int64, quorum int) (*BallotNum, error) { 126 | 127 | replies := p.rpcToAll(acceptorIds, "Accept") 128 | 129 | ok := 0 130 | higherBal := *p.Bal 131 | for _, r := range replies { 132 | pretty.Logf("Proposer: handling Accept reply: %s", r) 133 | if !p.Bal.GE(r.LastBal) { 134 | if r.LastBal.GE(&higherBal) { 135 | higherBal = *r.LastBal 136 | } 137 | continue 138 | } 139 | ok += 1 140 | if ok == quorum { 141 | return nil, nil 142 | } 143 | } 144 | 145 | return &higherBal, NotEnoughQuorum 146 | 147 | } 148 | 149 | // rpcToAll send Prepare or Accept RPC to the specified Acceptors. 150 | func (p *Proposer) rpcToAll(acceptorIds []int64, action string) []*Acceptor { 151 | 152 | replies := []*Acceptor{} 153 | 154 | for _, aid := range acceptorIds { 155 | var err error 156 | address := fmt.Sprintf("127.0.0.1:%d", AcceptorBasePort+int64(aid)) 157 | // Set up a connection to the server. 158 | conn, err := grpc.Dial(address, grpc.WithInsecure()) 159 | if err != nil { 160 | log.Fatalf("did not connect: %v", err) 161 | } 162 | 163 | defer conn.Close() 164 | c := NewPaxosKVClient(conn) 165 | 166 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 167 | defer cancel() 168 | 169 | var reply *Acceptor 170 | if action == "Prepare" { 171 | reply, err = c.Prepare(ctx, p) 172 | } else if action == "Accept" { 173 | reply, err = c.Accept(ctx, p) 174 | } 175 | if err != nil { 176 | log.Printf("Proposer: %s failure from Acceptor-%d: %v", action, aid, err) 177 | } 178 | log.Printf("Proposer: recv %s reply from: Acceptor-%d: %v", action, aid, reply) 179 | 180 | // hear may be nil if rpc inner err 181 | if reply != nil { 182 | replies = append(replies, reply) 183 | } 184 | } 185 | return replies 186 | } 187 | 188 | // Version defines one modification of a key-value record. 189 | // It is barely an Acceptor with a lock. 190 | type Version struct { 191 | mu sync.Mutex 192 | acceptor Acceptor 193 | } 194 | 195 | // Versions stores all versions of a record. 196 | // The value of every version is decided by a paxos instance, e.g. an Acceptor. 197 | type Versions map[int64]*Version 198 | 199 | // KVServer impl the paxos Acceptor API: handing Prepare and Accept request. 200 | type KVServer struct { 201 | UnimplementedPaxosKVServer 202 | mu sync.Mutex 203 | Storage map[string]Versions 204 | } 205 | 206 | func (s *KVServer) getLockedVersion(id *PaxosInstanceId) *Version { 207 | s.mu.Lock() 208 | defer s.mu.Unlock() 209 | 210 | key := id.Key 211 | ver := id.Ver 212 | rec, found := s.Storage[key] 213 | if !found { 214 | rec = Versions{} 215 | s.Storage[key] = rec 216 | } 217 | 218 | v, found := rec[ver] 219 | if !found { 220 | // initialize an empty paxos instance 221 | rec[ver] = &Version{ 222 | acceptor: Acceptor{ 223 | LastBal: &BallotNum{}, 224 | VBal: &BallotNum{}, 225 | }, 226 | } 227 | v = rec[ver] 228 | } 229 | 230 | pretty.Logf("Acceptor: getLockedVersion: %s", v) 231 | v.mu.Lock() 232 | 233 | return v 234 | } 235 | 236 | // Prepare handles Prepare request. 237 | // Handling Prepare needs only the `Bal` field. 238 | // The reply contains all fields of an Acceptor thus it just replies the 239 | // Acceptor itself as reply data structure. 240 | func (s *KVServer) Prepare(c context.Context, r *Proposer) (*Acceptor, error) { 241 | 242 | pretty.Logf("Acceptor: recv Prepare-request: %v", r) 243 | 244 | v := s.getLockedVersion(r.Id) 245 | defer v.mu.Unlock() 246 | reply := v.acceptor 247 | 248 | if r.Bal.GE(v.acceptor.LastBal) { 249 | v.acceptor.LastBal = r.Bal 250 | } 251 | 252 | return &reply, nil 253 | } 254 | 255 | // Accept handles Accept request. 256 | // The reply need only field `LastBal` but for simplicity we just use an 257 | // Acceptor as reply data structure. 258 | func (s *KVServer) Accept(c context.Context, r *Proposer) (*Acceptor, error) { 259 | 260 | pretty.Logf("Acceptor: recv Accept-request: %v", r) 261 | 262 | v := s.getLockedVersion(r.Id) 263 | defer v.mu.Unlock() 264 | 265 | // a := &X{} 266 | // `b := &*a` does not deref the reference, b and a are the same pointer. 267 | d := *v.acceptor.LastBal 268 | reply := Acceptor{ 269 | LastBal: &d, 270 | } 271 | 272 | // article say acceptor's LastBal equal proposer's Bal will accept it 273 | // but if greater, point that a large proposer's Bal has been through phrase1 with most acceptor, the same accept it 274 | if r.Bal.GE(v.acceptor.LastBal) { 275 | v.acceptor.LastBal = r.Bal 276 | v.acceptor.Val = r.Val 277 | v.acceptor.VBal = r.Bal 278 | } 279 | 280 | return &reply, nil 281 | } 282 | 283 | // ServeAcceptors starts a grpc server for every acceptor. 284 | func ServeAcceptors(acceptorIds []int64) []*grpc.Server { 285 | 286 | servers := []*grpc.Server{} 287 | 288 | for _, aid := range acceptorIds { 289 | addr := fmt.Sprintf(":%d", AcceptorBasePort+int64(aid)) 290 | 291 | lis, err := net.Listen("tcp", addr) 292 | if err != nil { 293 | log.Fatalf("listen: %s %v", addr, err) 294 | } 295 | 296 | s := grpc.NewServer() 297 | RegisterPaxosKVServer(s, &KVServer{ 298 | Storage: map[string]Versions{}, 299 | }) 300 | reflection.Register(s) 301 | pretty.Logf("Acceptor-%d serving on %s ...", aid, addr) 302 | servers = append(servers, s) 303 | go s.Serve(lis) 304 | } 305 | 306 | return servers 307 | } 308 | -------------------------------------------------------------------------------- /paxoskv/impl_test.go: -------------------------------------------------------------------------------- 1 | package paxoskv 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestAcceptor_Accept_deref_LastBal(t *testing.T) { 10 | 11 | ta := require.New(t) 12 | 13 | kvs := KVServer{ 14 | Storage: map[string]Versions{}, 15 | } 16 | p := &Proposer{ 17 | Id: &PaxosInstanceId{ 18 | Key: "x", 19 | Ver: 0, 20 | }, 21 | // smaller than any bal 22 | Bal: &BallotNum{N: -1}, 23 | } 24 | 25 | reply, err := kvs.Accept(nil, p) 26 | _ = err 27 | 28 | v := kvs.Storage["x"][0] 29 | 30 | // change storage, the reply should not be affected 31 | v.acceptor.LastBal.N = 100 32 | ta.Equal(int64(0), reply.LastBal.N) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /paxoskv/paxos_slides_case_test.go: -------------------------------------------------------------------------------- 1 | package paxoskv 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/golang/protobuf/proto" 9 | ) 10 | 11 | func TestCase1SingleProposer(t *testing.T) { 12 | 13 | // slide-32: 1 Proposer, 3 Acceptor, only two of them are involved. 14 | // The Proposer finishes a paxos without conflict. 15 | 16 | ta := require.New(t) 17 | 18 | acceptorIds := []int64{0, 1, 2} 19 | quorum := 2 20 | 21 | servers := ServeAcceptors(acceptorIds) 22 | defer func() { 23 | for _, s := range servers { 24 | s.Stop() 25 | } 26 | }() 27 | 28 | // The proposer try to set i₀ = 10 29 | var val int64 = 10 30 | paxosId := &PaxosInstanceId{ 31 | Key: "i", 32 | Ver: 0, 33 | } 34 | 35 | // proposer X 36 | var pidx int64 = 10 37 | px := Proposer{ 38 | Id: paxosId, 39 | Bal: &BallotNum{N: 0, ProposerId: pidx}, 40 | } 41 | 42 | // Phase 1 will be done without seeing other ballot, nor other voted value. 43 | latestVal, higherBal, err := px.Phase1([]int64{0, 1}, quorum) 44 | ta.Nil(err, "constituted a quorum") 45 | ta.Nil(higherBal, "no other proposer is seen") 46 | ta.Nil(latestVal, "no voted value") 47 | 48 | // Thus the Proposer choose a new value to propose. 49 | px.Val = &Value{Vi64: val} 50 | 51 | // Phase 2 52 | higherBal, err = px.Phase2([]int64{0, 1}, quorum) 53 | ta.Nil(err, "constituted a quorum") 54 | ta.Nil(higherBal, "no other proposer is seen") 55 | } 56 | 57 | func TestCase2DoubleProposer(t *testing.T) { 58 | 59 | // slide-33: 2 Proposer X and Y, 3 Acceptor. 60 | // Y overrides X then successfully decided a value. 61 | // Then X re-run paxos with a higher ballot and then proposed the value Y 62 | // chose. 63 | 64 | ta := require.New(t) 65 | 66 | acceptorIds := []int64{0, 1, 2} 67 | quorum := 2 68 | 69 | servers := ServeAcceptors(acceptorIds) 70 | defer func() { 71 | for _, s := range servers { 72 | s.Stop() 73 | } 74 | }() 75 | 76 | // two proposer 77 | var pidx int64 = 10 78 | var pidy int64 = 11 79 | 80 | paxosId := &PaxosInstanceId{ 81 | Key: "i", 82 | Ver: 0, 83 | } 84 | 85 | // Proposer X prepared on Acceptor 0, 1 with ballot (1, pidx) and succeed. 86 | 87 | px := Proposer{ 88 | Id: paxosId, 89 | Bal: &BallotNum{N: 1, ProposerId: pidx}, 90 | } 91 | latestVal, higherBal, err := px.Phase1([]int64{0, 1}, quorum) 92 | ta.True(err == nil && higherBal == nil && latestVal == nil, "succeess") 93 | 94 | // Proposer Y prepared on Acceptor 1, 2 with a higher ballot(2, pidy) and 95 | // succeed too, by overriding ballot for X. 96 | 97 | py := Proposer{ 98 | Id: paxosId, 99 | Bal: &BallotNum{N: 2, ProposerId: pidy}, 100 | } 101 | latestVal, higherBal, err = py.Phase1([]int64{1, 2}, quorum) 102 | ta.True(err == nil && higherBal == nil && latestVal == nil, "succeess") 103 | 104 | // Proposer X does not know of Y, it chooses the value it wants to 105 | // write and proceed phase-2 on Acceptor 0, 1. 106 | // Then X found a higher ballot thus it failed to finish the paxos algo. 107 | 108 | px.Val = &Value{Vi64: 100} 109 | higherBal, err = px.Phase2([]int64{0, 1}, quorum) 110 | ta.Equalf(err, NotEnoughQuorum, "Proposer X should fail in phase-2") 111 | ta.True(proto.Equal(higherBal, py.Bal), 112 | "X should seen a higher bal, which is written by Y") 113 | 114 | // Proposer Y does not know of X. 115 | // But it has a higher ballot thus it would succeed running phase-2 116 | 117 | py.Val = &Value{Vi64: 200} 118 | higherBal, err = py.Phase2([]int64{1, 2}, quorum) 119 | ta.Nil(err, "Proposer Y succeeds in phase-2") 120 | ta.Nil(higherBal, "Y would not see a higher bal") 121 | 122 | // Proposer X retry with a higher ballot (3, pidx). 123 | // It will see a voted value by Y then choose it to propose. 124 | // Finally X finished the paxos but it did not propose the value it wants 125 | // to. 126 | 127 | px.Val = nil 128 | px.Bal = &BallotNum{N: 3, ProposerId: pidx} 129 | latestVal, higherBal, err = px.Phase1([]int64{0, 1}, quorum) 130 | ta.Nil(err, "constituted a quorum") 131 | ta.Nil(higherBal, "X should not see other bal") 132 | ta.True(proto.Equal(latestVal, py.Val), 133 | "X should see the value Acceptor voted for Y") 134 | 135 | // Proposer X then propose the seen value and finish phase-2 136 | 137 | px.Val = latestVal 138 | higherBal, err = px.Phase2([]int64{0, 1}, quorum) 139 | ta.Nil(err, "Proposer X should succeed in phase-2") 140 | ta.Nil(higherBal, "X should succeed") 141 | 142 | } 143 | -------------------------------------------------------------------------------- /paxoskv/paxoskv.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.27.1 4 | // protoc v3.17.3 5 | // source: paxoskv.proto 6 | 7 | package paxoskv 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 15 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 16 | reflect "reflect" 17 | sync "sync" 18 | ) 19 | 20 | const ( 21 | // Verify that this generated code is sufficiently up-to-date. 22 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 23 | // Verify that runtime/protoimpl is sufficiently up-to-date. 24 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 25 | ) 26 | 27 | // BallotNum is the ballot number in paxos. It consists of a monotonically 28 | // incremental number and a universally unique ProposerId. 29 | type BallotNum struct { 30 | state protoimpl.MessageState 31 | sizeCache protoimpl.SizeCache 32 | unknownFields protoimpl.UnknownFields 33 | 34 | N int64 `protobuf:"varint,1,opt,name=N,proto3" json:"N,omitempty"` 35 | ProposerId int64 `protobuf:"varint,2,opt,name=ProposerId,proto3" json:"ProposerId,omitempty"` 36 | } 37 | 38 | func (x *BallotNum) Reset() { 39 | *x = BallotNum{} 40 | if protoimpl.UnsafeEnabled { 41 | mi := &file_paxoskv_proto_msgTypes[0] 42 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 43 | ms.StoreMessageInfo(mi) 44 | } 45 | } 46 | 47 | func (x *BallotNum) String() string { 48 | return protoimpl.X.MessageStringOf(x) 49 | } 50 | 51 | func (*BallotNum) ProtoMessage() {} 52 | 53 | func (x *BallotNum) ProtoReflect() protoreflect.Message { 54 | mi := &file_paxoskv_proto_msgTypes[0] 55 | if protoimpl.UnsafeEnabled && x != nil { 56 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 57 | if ms.LoadMessageInfo() == nil { 58 | ms.StoreMessageInfo(mi) 59 | } 60 | return ms 61 | } 62 | return mi.MessageOf(x) 63 | } 64 | 65 | // Deprecated: Use BallotNum.ProtoReflect.Descriptor instead. 66 | func (*BallotNum) Descriptor() ([]byte, []int) { 67 | return file_paxoskv_proto_rawDescGZIP(), []int{0} 68 | } 69 | 70 | func (x *BallotNum) GetN() int64 { 71 | if x != nil { 72 | return x.N 73 | } 74 | return 0 75 | } 76 | 77 | func (x *BallotNum) GetProposerId() int64 { 78 | if x != nil { 79 | return x.ProposerId 80 | } 81 | return 0 82 | } 83 | 84 | // Value is the value part of a key-value record. 85 | // In this demo it is just a int64 86 | type Value struct { 87 | state protoimpl.MessageState 88 | sizeCache protoimpl.SizeCache 89 | unknownFields protoimpl.UnknownFields 90 | 91 | Vi64 int64 `protobuf:"varint,1,opt,name=Vi64,proto3" json:"Vi64,omitempty"` 92 | } 93 | 94 | func (x *Value) Reset() { 95 | *x = Value{} 96 | if protoimpl.UnsafeEnabled { 97 | mi := &file_paxoskv_proto_msgTypes[1] 98 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 99 | ms.StoreMessageInfo(mi) 100 | } 101 | } 102 | 103 | func (x *Value) String() string { 104 | return protoimpl.X.MessageStringOf(x) 105 | } 106 | 107 | func (*Value) ProtoMessage() {} 108 | 109 | func (x *Value) ProtoReflect() protoreflect.Message { 110 | mi := &file_paxoskv_proto_msgTypes[1] 111 | if protoimpl.UnsafeEnabled && x != nil { 112 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 113 | if ms.LoadMessageInfo() == nil { 114 | ms.StoreMessageInfo(mi) 115 | } 116 | return ms 117 | } 118 | return mi.MessageOf(x) 119 | } 120 | 121 | // Deprecated: Use Value.ProtoReflect.Descriptor instead. 122 | func (*Value) Descriptor() ([]byte, []int) { 123 | return file_paxoskv_proto_rawDescGZIP(), []int{1} 124 | } 125 | 126 | func (x *Value) GetVi64() int64 { 127 | if x != nil { 128 | return x.Vi64 129 | } 130 | return 0 131 | } 132 | 133 | // PaxosInstanceId specifies what paxos instance it runs on. 134 | // A paxos instance is used to determine a specific version of a record. 135 | // E.g.: for a key-value record foo₀=0, to set foo=2, a paxos instance is 136 | // created to choose the value for key "foo", ver "1", i.e., foo₁ 137 | type PaxosInstanceId struct { 138 | state protoimpl.MessageState 139 | sizeCache protoimpl.SizeCache 140 | unknownFields protoimpl.UnknownFields 141 | 142 | // the key of a record to operate on. 143 | Key string `protobuf:"bytes,1,opt,name=Key,proto3" json:"Key,omitempty"` 144 | // the version of the record to modify. 145 | Ver int64 `protobuf:"varint,2,opt,name=Ver,proto3" json:"Ver,omitempty"` 146 | } 147 | 148 | func (x *PaxosInstanceId) Reset() { 149 | *x = PaxosInstanceId{} 150 | if protoimpl.UnsafeEnabled { 151 | mi := &file_paxoskv_proto_msgTypes[2] 152 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 153 | ms.StoreMessageInfo(mi) 154 | } 155 | } 156 | 157 | func (x *PaxosInstanceId) String() string { 158 | return protoimpl.X.MessageStringOf(x) 159 | } 160 | 161 | func (*PaxosInstanceId) ProtoMessage() {} 162 | 163 | func (x *PaxosInstanceId) ProtoReflect() protoreflect.Message { 164 | mi := &file_paxoskv_proto_msgTypes[2] 165 | if protoimpl.UnsafeEnabled && x != nil { 166 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 167 | if ms.LoadMessageInfo() == nil { 168 | ms.StoreMessageInfo(mi) 169 | } 170 | return ms 171 | } 172 | return mi.MessageOf(x) 173 | } 174 | 175 | // Deprecated: Use PaxosInstanceId.ProtoReflect.Descriptor instead. 176 | func (*PaxosInstanceId) Descriptor() ([]byte, []int) { 177 | return file_paxoskv_proto_rawDescGZIP(), []int{2} 178 | } 179 | 180 | func (x *PaxosInstanceId) GetKey() string { 181 | if x != nil { 182 | return x.Key 183 | } 184 | return "" 185 | } 186 | 187 | func (x *PaxosInstanceId) GetVer() int64 { 188 | if x != nil { 189 | return x.Ver 190 | } 191 | return 0 192 | } 193 | 194 | // Acceptor is the state of an Acceptor and also serves as the reply of the 195 | // Prepare/Accept. 196 | type Acceptor struct { 197 | state protoimpl.MessageState 198 | sizeCache protoimpl.SizeCache 199 | unknownFields protoimpl.UnknownFields 200 | 201 | // the last ballot number the instance knows of. 202 | LastBal *BallotNum `protobuf:"bytes,1,opt,name=LastBal,proto3" json:"LastBal,omitempty"` 203 | // the voted value by this Acceptor 204 | Val *Value `protobuf:"bytes,2,opt,name=Val,proto3" json:"Val,omitempty"` 205 | // at which ballot number the Acceptor voted it. 206 | VBal *BallotNum `protobuf:"bytes,3,opt,name=VBal,proto3" json:"VBal,omitempty"` 207 | } 208 | 209 | func (x *Acceptor) Reset() { 210 | *x = Acceptor{} 211 | if protoimpl.UnsafeEnabled { 212 | mi := &file_paxoskv_proto_msgTypes[3] 213 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 214 | ms.StoreMessageInfo(mi) 215 | } 216 | } 217 | 218 | func (x *Acceptor) String() string { 219 | return protoimpl.X.MessageStringOf(x) 220 | } 221 | 222 | func (*Acceptor) ProtoMessage() {} 223 | 224 | func (x *Acceptor) ProtoReflect() protoreflect.Message { 225 | mi := &file_paxoskv_proto_msgTypes[3] 226 | if protoimpl.UnsafeEnabled && x != nil { 227 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 228 | if ms.LoadMessageInfo() == nil { 229 | ms.StoreMessageInfo(mi) 230 | } 231 | return ms 232 | } 233 | return mi.MessageOf(x) 234 | } 235 | 236 | // Deprecated: Use Acceptor.ProtoReflect.Descriptor instead. 237 | func (*Acceptor) Descriptor() ([]byte, []int) { 238 | return file_paxoskv_proto_rawDescGZIP(), []int{3} 239 | } 240 | 241 | func (x *Acceptor) GetLastBal() *BallotNum { 242 | if x != nil { 243 | return x.LastBal 244 | } 245 | return nil 246 | } 247 | 248 | func (x *Acceptor) GetVal() *Value { 249 | if x != nil { 250 | return x.Val 251 | } 252 | return nil 253 | } 254 | 255 | func (x *Acceptor) GetVBal() *BallotNum { 256 | if x != nil { 257 | return x.VBal 258 | } 259 | return nil 260 | } 261 | 262 | // Proposer is the state of a Proposer and also serves as the request of 263 | // Prepare/Accept. 264 | type Proposer struct { 265 | state protoimpl.MessageState 266 | sizeCache protoimpl.SizeCache 267 | unknownFields protoimpl.UnknownFields 268 | 269 | // what paxos instance it runs on 270 | Id *PaxosInstanceId `protobuf:"bytes,1,opt,name=Id,proto3" json:"Id,omitempty"` 271 | // Bal is the ballot number of a Proposer 272 | Bal *BallotNum `protobuf:"bytes,2,opt,name=Bal,proto3" json:"Bal,omitempty"` 273 | // Val is the value a Proposer has chosen. 274 | Val *Value `protobuf:"bytes,3,opt,name=Val,proto3" json:"Val,omitempty"` 275 | } 276 | 277 | func (x *Proposer) Reset() { 278 | *x = Proposer{} 279 | if protoimpl.UnsafeEnabled { 280 | mi := &file_paxoskv_proto_msgTypes[4] 281 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 282 | ms.StoreMessageInfo(mi) 283 | } 284 | } 285 | 286 | func (x *Proposer) String() string { 287 | return protoimpl.X.MessageStringOf(x) 288 | } 289 | 290 | func (*Proposer) ProtoMessage() {} 291 | 292 | func (x *Proposer) ProtoReflect() protoreflect.Message { 293 | mi := &file_paxoskv_proto_msgTypes[4] 294 | if protoimpl.UnsafeEnabled && x != nil { 295 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 296 | if ms.LoadMessageInfo() == nil { 297 | ms.StoreMessageInfo(mi) 298 | } 299 | return ms 300 | } 301 | return mi.MessageOf(x) 302 | } 303 | 304 | // Deprecated: Use Proposer.ProtoReflect.Descriptor instead. 305 | func (*Proposer) Descriptor() ([]byte, []int) { 306 | return file_paxoskv_proto_rawDescGZIP(), []int{4} 307 | } 308 | 309 | func (x *Proposer) GetId() *PaxosInstanceId { 310 | if x != nil { 311 | return x.Id 312 | } 313 | return nil 314 | } 315 | 316 | func (x *Proposer) GetBal() *BallotNum { 317 | if x != nil { 318 | return x.Bal 319 | } 320 | return nil 321 | } 322 | 323 | func (x *Proposer) GetVal() *Value { 324 | if x != nil { 325 | return x.Val 326 | } 327 | return nil 328 | } 329 | 330 | var File_paxoskv_proto protoreflect.FileDescriptor 331 | 332 | var file_paxoskv_proto_rawDesc = []byte{ 333 | 0x0a, 0x0d, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 334 | 0x07, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x22, 0x39, 0x0a, 0x09, 0x42, 0x61, 0x6c, 0x6c, 335 | 0x6f, 0x74, 0x4e, 0x75, 0x6d, 0x12, 0x0c, 0x0a, 0x01, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 336 | 0x52, 0x01, 0x4e, 0x12, 0x1e, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x49, 337 | 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 338 | 0x72, 0x49, 0x64, 0x22, 0x1b, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 339 | 0x56, 0x69, 0x36, 0x34, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x56, 0x69, 0x36, 0x34, 340 | 0x22, 0x35, 0x0a, 0x0f, 0x50, 0x61, 0x78, 0x6f, 0x73, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 341 | 0x65, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 342 | 0x52, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x56, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 343 | 0x28, 0x03, 0x52, 0x03, 0x56, 0x65, 0x72, 0x22, 0x82, 0x01, 0x0a, 0x08, 0x41, 0x63, 0x63, 0x65, 344 | 0x70, 0x74, 0x6f, 0x72, 0x12, 0x2c, 0x0a, 0x07, 0x4c, 0x61, 0x73, 0x74, 0x42, 0x61, 0x6c, 0x18, 345 | 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x2e, 346 | 0x42, 0x61, 0x6c, 0x6c, 0x6f, 0x74, 0x4e, 0x75, 0x6d, 0x52, 0x07, 0x4c, 0x61, 0x73, 0x74, 0x42, 347 | 0x61, 0x6c, 0x12, 0x20, 0x0a, 0x03, 0x56, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 348 | 0x0e, 0x2e, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 349 | 0x03, 0x56, 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x04, 0x56, 0x42, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 350 | 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x2e, 0x42, 0x61, 0x6c, 351 | 0x6c, 0x6f, 0x74, 0x4e, 0x75, 0x6d, 0x52, 0x04, 0x56, 0x42, 0x61, 0x6c, 0x22, 0x7c, 0x0a, 0x08, 352 | 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 353 | 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x2e, 0x50, 354 | 0x61, 0x78, 0x6f, 0x73, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x52, 0x02, 355 | 0x49, 0x64, 0x12, 0x24, 0x0a, 0x03, 0x42, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 356 | 0x12, 0x2e, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x2e, 0x42, 0x61, 0x6c, 0x6c, 0x6f, 0x74, 357 | 0x4e, 0x75, 0x6d, 0x52, 0x03, 0x42, 0x61, 0x6c, 0x12, 0x20, 0x0a, 0x03, 0x56, 0x61, 0x6c, 0x18, 358 | 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x2e, 359 | 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x56, 0x61, 0x6c, 0x32, 0x6e, 0x0a, 0x07, 0x50, 0x61, 360 | 0x78, 0x6f, 0x73, 0x4b, 0x56, 0x12, 0x31, 0x0a, 0x07, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 361 | 0x12, 0x11, 0x2e, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 362 | 0x73, 0x65, 0x72, 0x1a, 0x11, 0x2e, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x2e, 0x41, 0x63, 363 | 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x22, 0x00, 0x12, 0x30, 0x0a, 0x06, 0x41, 0x63, 0x63, 0x65, 364 | 0x70, 0x74, 0x12, 0x11, 0x2e, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x2e, 0x50, 0x72, 0x6f, 365 | 0x70, 0x6f, 0x73, 0x65, 0x72, 0x1a, 0x11, 0x2e, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x2e, 366 | 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x22, 0x00, 0x42, 0x1d, 0x5a, 0x1b, 0x67, 0x69, 367 | 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x63, 0x69, 368 | 0x64, 0x2f, 0x70, 0x61, 0x78, 0x6f, 0x73, 0x6b, 0x76, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 369 | 0x33, 370 | } 371 | 372 | var ( 373 | file_paxoskv_proto_rawDescOnce sync.Once 374 | file_paxoskv_proto_rawDescData = file_paxoskv_proto_rawDesc 375 | ) 376 | 377 | func file_paxoskv_proto_rawDescGZIP() []byte { 378 | file_paxoskv_proto_rawDescOnce.Do(func() { 379 | file_paxoskv_proto_rawDescData = protoimpl.X.CompressGZIP(file_paxoskv_proto_rawDescData) 380 | }) 381 | return file_paxoskv_proto_rawDescData 382 | } 383 | 384 | var file_paxoskv_proto_msgTypes = make([]protoimpl.MessageInfo, 5) 385 | var file_paxoskv_proto_goTypes = []interface{}{ 386 | (*BallotNum)(nil), // 0: paxoskv.BallotNum 387 | (*Value)(nil), // 1: paxoskv.Value 388 | (*PaxosInstanceId)(nil), // 2: paxoskv.PaxosInstanceId 389 | (*Acceptor)(nil), // 3: paxoskv.Acceptor 390 | (*Proposer)(nil), // 4: paxoskv.Proposer 391 | } 392 | var file_paxoskv_proto_depIdxs = []int32{ 393 | 0, // 0: paxoskv.Acceptor.LastBal:type_name -> paxoskv.BallotNum 394 | 1, // 1: paxoskv.Acceptor.Val:type_name -> paxoskv.Value 395 | 0, // 2: paxoskv.Acceptor.VBal:type_name -> paxoskv.BallotNum 396 | 2, // 3: paxoskv.Proposer.Id:type_name -> paxoskv.PaxosInstanceId 397 | 0, // 4: paxoskv.Proposer.Bal:type_name -> paxoskv.BallotNum 398 | 1, // 5: paxoskv.Proposer.Val:type_name -> paxoskv.Value 399 | 4, // 6: paxoskv.PaxosKV.Prepare:input_type -> paxoskv.Proposer 400 | 4, // 7: paxoskv.PaxosKV.Accept:input_type -> paxoskv.Proposer 401 | 3, // 8: paxoskv.PaxosKV.Prepare:output_type -> paxoskv.Acceptor 402 | 3, // 9: paxoskv.PaxosKV.Accept:output_type -> paxoskv.Acceptor 403 | 8, // [8:10] is the sub-list for method output_type 404 | 6, // [6:8] is the sub-list for method input_type 405 | 6, // [6:6] is the sub-list for extension type_name 406 | 6, // [6:6] is the sub-list for extension extendee 407 | 0, // [0:6] is the sub-list for field type_name 408 | } 409 | 410 | func init() { file_paxoskv_proto_init() } 411 | func file_paxoskv_proto_init() { 412 | if File_paxoskv_proto != nil { 413 | return 414 | } 415 | if !protoimpl.UnsafeEnabled { 416 | file_paxoskv_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 417 | switch v := v.(*BallotNum); i { 418 | case 0: 419 | return &v.state 420 | case 1: 421 | return &v.sizeCache 422 | case 2: 423 | return &v.unknownFields 424 | default: 425 | return nil 426 | } 427 | } 428 | file_paxoskv_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 429 | switch v := v.(*Value); i { 430 | case 0: 431 | return &v.state 432 | case 1: 433 | return &v.sizeCache 434 | case 2: 435 | return &v.unknownFields 436 | default: 437 | return nil 438 | } 439 | } 440 | file_paxoskv_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 441 | switch v := v.(*PaxosInstanceId); i { 442 | case 0: 443 | return &v.state 444 | case 1: 445 | return &v.sizeCache 446 | case 2: 447 | return &v.unknownFields 448 | default: 449 | return nil 450 | } 451 | } 452 | file_paxoskv_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 453 | switch v := v.(*Acceptor); i { 454 | case 0: 455 | return &v.state 456 | case 1: 457 | return &v.sizeCache 458 | case 2: 459 | return &v.unknownFields 460 | default: 461 | return nil 462 | } 463 | } 464 | file_paxoskv_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { 465 | switch v := v.(*Proposer); i { 466 | case 0: 467 | return &v.state 468 | case 1: 469 | return &v.sizeCache 470 | case 2: 471 | return &v.unknownFields 472 | default: 473 | return nil 474 | } 475 | } 476 | } 477 | type x struct{} 478 | out := protoimpl.TypeBuilder{ 479 | File: protoimpl.DescBuilder{ 480 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 481 | RawDescriptor: file_paxoskv_proto_rawDesc, 482 | NumEnums: 0, 483 | NumMessages: 5, 484 | NumExtensions: 0, 485 | NumServices: 1, 486 | }, 487 | GoTypes: file_paxoskv_proto_goTypes, 488 | DependencyIndexes: file_paxoskv_proto_depIdxs, 489 | MessageInfos: file_paxoskv_proto_msgTypes, 490 | }.Build() 491 | File_paxoskv_proto = out.File 492 | file_paxoskv_proto_rawDesc = nil 493 | file_paxoskv_proto_goTypes = nil 494 | file_paxoskv_proto_depIdxs = nil 495 | } 496 | 497 | // Reference imports to suppress errors if they are not otherwise used. 498 | var _ context.Context 499 | var _ grpc.ClientConnInterface 500 | 501 | // This is a compile-time assertion to ensure that this generated file 502 | // is compatible with the grpc package it is being compiled against. 503 | const _ = grpc.SupportPackageIsVersion6 504 | 505 | // PaxosKVClient is the client API for PaxosKV service. 506 | // 507 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 508 | type PaxosKVClient interface { 509 | Prepare(ctx context.Context, in *Proposer, opts ...grpc.CallOption) (*Acceptor, error) 510 | Accept(ctx context.Context, in *Proposer, opts ...grpc.CallOption) (*Acceptor, error) 511 | } 512 | 513 | type paxosKVClient struct { 514 | cc grpc.ClientConnInterface 515 | } 516 | 517 | func NewPaxosKVClient(cc grpc.ClientConnInterface) PaxosKVClient { 518 | return &paxosKVClient{cc} 519 | } 520 | 521 | func (c *paxosKVClient) Prepare(ctx context.Context, in *Proposer, opts ...grpc.CallOption) (*Acceptor, error) { 522 | out := new(Acceptor) 523 | err := c.cc.Invoke(ctx, "/paxoskv.PaxosKV/Prepare", in, out, opts...) 524 | if err != nil { 525 | return nil, err 526 | } 527 | return out, nil 528 | } 529 | 530 | func (c *paxosKVClient) Accept(ctx context.Context, in *Proposer, opts ...grpc.CallOption) (*Acceptor, error) { 531 | out := new(Acceptor) 532 | err := c.cc.Invoke(ctx, "/paxoskv.PaxosKV/Accept", in, out, opts...) 533 | if err != nil { 534 | return nil, err 535 | } 536 | return out, nil 537 | } 538 | 539 | // PaxosKVServer is the server API for PaxosKV service. 540 | type PaxosKVServer interface { 541 | Prepare(context.Context, *Proposer) (*Acceptor, error) 542 | Accept(context.Context, *Proposer) (*Acceptor, error) 543 | } 544 | 545 | // UnimplementedPaxosKVServer can be embedded to have forward compatible implementations. 546 | type UnimplementedPaxosKVServer struct { 547 | } 548 | 549 | func (*UnimplementedPaxosKVServer) Prepare(context.Context, *Proposer) (*Acceptor, error) { 550 | return nil, status.Errorf(codes.Unimplemented, "method Prepare not implemented") 551 | } 552 | func (*UnimplementedPaxosKVServer) Accept(context.Context, *Proposer) (*Acceptor, error) { 553 | return nil, status.Errorf(codes.Unimplemented, "method Accept not implemented") 554 | } 555 | 556 | func RegisterPaxosKVServer(s *grpc.Server, srv PaxosKVServer) { 557 | s.RegisterService(&_PaxosKV_serviceDesc, srv) 558 | } 559 | 560 | func _PaxosKV_Prepare_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 561 | in := new(Proposer) 562 | if err := dec(in); err != nil { 563 | return nil, err 564 | } 565 | if interceptor == nil { 566 | return srv.(PaxosKVServer).Prepare(ctx, in) 567 | } 568 | info := &grpc.UnaryServerInfo{ 569 | Server: srv, 570 | FullMethod: "/paxoskv.PaxosKV/Prepare", 571 | } 572 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 573 | return srv.(PaxosKVServer).Prepare(ctx, req.(*Proposer)) 574 | } 575 | return interceptor(ctx, in, info, handler) 576 | } 577 | 578 | func _PaxosKV_Accept_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 579 | in := new(Proposer) 580 | if err := dec(in); err != nil { 581 | return nil, err 582 | } 583 | if interceptor == nil { 584 | return srv.(PaxosKVServer).Accept(ctx, in) 585 | } 586 | info := &grpc.UnaryServerInfo{ 587 | Server: srv, 588 | FullMethod: "/paxoskv.PaxosKV/Accept", 589 | } 590 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 591 | return srv.(PaxosKVServer).Accept(ctx, req.(*Proposer)) 592 | } 593 | return interceptor(ctx, in, info, handler) 594 | } 595 | 596 | var _PaxosKV_serviceDesc = grpc.ServiceDesc{ 597 | ServiceName: "paxoskv.PaxosKV", 598 | HandlerType: (*PaxosKVServer)(nil), 599 | Methods: []grpc.MethodDesc{ 600 | { 601 | MethodName: "Prepare", 602 | Handler: _PaxosKV_Prepare_Handler, 603 | }, 604 | { 605 | MethodName: "Accept", 606 | Handler: _PaxosKV_Accept_Handler, 607 | }, 608 | }, 609 | Streams: []grpc.StreamDesc{}, 610 | Metadata: "paxoskv.proto", 611 | } 612 | -------------------------------------------------------------------------------- /proto/paxoskv.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package paxoskv; 4 | 5 | option go_package = "github.com/openacid/paxoskv"; 6 | 7 | // PaxosKV defines the paxos RPC. 8 | // 9 | // A Proposer sends all its fields in a Prepare request, with `Val` being left a nil. 10 | // A Proposer sends all its fields in an Accept request, with `Val` being filled with 11 | // the value it chose. 12 | // 13 | // An Acceptor responds all its fields in a Prepare reply. 14 | // An Acceptor responds `LastBal` fields in a Accept reply. 15 | // 16 | // Thus we just use the struct of a Proposer as request struct. 17 | // And the struct of an Acceptor as reply struct. 18 | service PaxosKV { 19 | rpc Prepare (Proposer) returns (Acceptor) {} 20 | rpc Accept (Proposer) returns (Acceptor) {} 21 | } 22 | 23 | // BallotNum is the ballot number in paxos. It consists of a monotonically 24 | // incremental number and a universally unique ProposerId. 25 | message BallotNum { 26 | int64 N = 1; 27 | int64 ProposerId = 2; 28 | } 29 | 30 | // Value is the value part of a key-value record. 31 | // In this demo it is just a int64 32 | message Value { 33 | int64 Vi64 = 1; 34 | } 35 | 36 | // PaxosInstanceId specifies what paxos instance it runs on. 37 | // A paxos instance is used to determine a specific version of a record. 38 | // E.g.: for a key-value record foo₀=0, to set foo=2, a paxos instance is 39 | // created to choose the value for key "foo", ver "1", i.e., foo₁ 40 | message PaxosInstanceId { 41 | // the key of a record to operate on. 42 | string Key = 1; 43 | 44 | // the version of the record to modify. 45 | int64 Ver = 2; 46 | } 47 | 48 | // Acceptor is the state of an Acceptor and also serves as the reply of the 49 | // Prepare/Accept. 50 | message Acceptor { 51 | // the last ballot number the instance knows of. 52 | BallotNum LastBal = 1; 53 | 54 | // the voted value by this Acceptor 55 | Value Val = 2; 56 | 57 | // at which ballot number the Acceptor voted it. 58 | BallotNum VBal = 3; 59 | } 60 | 61 | // Proposer is the state of a Proposer and also serves as the request of 62 | // Prepare/Accept. 63 | message Proposer { 64 | // what paxos instance it runs on 65 | PaxosInstanceId Id = 1; 66 | 67 | // Bal is the ballot number of a Proposer 68 | BallotNum Bal = 2; 69 | 70 | // Val is the value a Proposer has chosen. 71 | Value Val = 3; 72 | } 73 | --------------------------------------------------------------------------------