2021/5/28
Overview
TiKV使用Key-Value模型保存数据,并且提供有序遍历方法。可以看做一个按照Key的二进制顺序有序的巨大Map。使用开源的RocksDB作为单机存储引擎
Raft协议
Raft协议的每个副本都会处于三种状态之一:Leader、Follower、Candidate
- Leader:所有请求的处理者,接受client的更新请求,本地处理后再同步给多个其他的副本
- Follower:请求的被动更新着,从Leader接受更新请求,让后写入本地日志文件
- Candidate:如果Follower在一定时间内没有收到Leader的心跳,则判断Leader可能已经故障,此时从新启动选主过程,此时副本会变成Candidate状态,直到选主结束
时间被分成很多连续的随机长度的term,term有唯一的id。每一个term一开始就进行选主。会有三种结果:
- 自己被选成了主。收到majority的投票后,状态切换为Leader,并定期给其他Server发心跳消息,别的Server收到消息后将状态切换为Follower
- 别人成为了主。将自己的state切换为Follower,并且更新本地的current_term_id
- 没有选出主。投票被瓜分,没有Leader被选出,投票过程将超时,会增加current_term_id发起新一轮投票
Leader被选出来后,接收客户端发来的请求,将它作为一个log entry添加到日志中,然后给其他Server发AppendEntriesRPC请求。当大多数副本已经将该命令写入到日志中,则执行这条log entry并返回结果到客户端。Raft保证一条commited(大多数副本写盘)的log entry持久化并且会被所有的节点执行。
Leader刚选出来的时候,它的日志可能和其他Follower的不一致,有一定机制来保证日志的一致性。
为了避免日志无限的增长,Raft会对整个系统进行snapshot,snapshot之前的日志都可以丢弃。在实际使用中snapshot频率需要合适。
Raft考虑了集群的拓扑变化,支持增加/减少副本数,节点替换等。
Region
为了实现存储的水平扩展,需要将数据分散在多台机器上,TiKV将整个Key-Value空间分成很多端,每一段是一系列连续的Key,称作Region。数据会以Region为单位分散在集群中的所有节点上,并且保证每个节点上服务的Region数量差不多,同时以Region为单位做Raft的复制和成员管理
2021/5/29
调度的需求
作为一个分布式高可用系统,必须满足的需求有:
- 副本数量不能多也不能少
- 副本需要分布在不同的机器上
- 新加节点后,可以将其他节点上的副本迁移过来
- 节点下线后,需要将该节点的数据迁移走
作为一个良好的分布式系统,需要优化的地方有:
- 维持整个集群的Leader分布均匀
- 维持每个节点的储存容量均匀
- 维持访问热点分布均匀
- 控制Balance的速度,避免影响在线服务
- 管理节点状态,包括手动上线/下线节点,以及自动下线失效节点
为了满足这些需求,需要收集足够的信息,比如每个节点的状态,Raft Group的信息,业务访问操作的统计等。PD根据这些信息以及调度的策略,制定出尽量满足前面所述需求的调度计划。
调度的基本操作
调度需求看似复杂,整理下来无非是以下三件事:
- 增加一个Replica
- 删除一个Replica
- 将Leader角色在一个Raft Group的不同Replica之间transfer
信息收集
- 每个TiKV节点会定期向PD汇报节点的整体信息(是否存活,是否有新加入的Store,状态信息)
- 每个Raft Group的Leader会定期向PD汇报信息(Region的状态)
调度的策略
- 一个Region的Replica数量正确(多删少加)
- 一个Raft Group中的多个Replica不在同一个位置
- 副本在Store之间均匀分配
- Leader数量在Store之间均匀分配
- 访问热点适量在Store之间均匀分配
- 各个Store存储空间的占用大致相等
- 控制调度速度,避免影响在线服务
- 支持手动下线节点
2021/5/30
Go
1 | package main |
- 大写字母开头的标识符对象可以被外部包的代码所使用,如果以小写字母开头,则对包外是不可见的
- 变量声明:
var identifier type
或v_name := value
自行推断数据类型 - 函数可以返回多个值
1
2
3func swap(x, y string) (string, string) {
return y, x
} - 函数闭包
1
2
3
4
5
6
7func getSequence() func() int {
i:=0
return func() int {
i+=1
return i
}
} - 方法:包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针
1
2
3func (variable_name variable_data_type) function_name() [return_type]{
/* 函数体*/
} - 声明数组
1
var variable_name [SIZE] variable_type
- 空指针是
nil
- 结构体可以用key: value赋值
1
2
3
4
5
6
7
8type Books struct {
title string
author string
subject string
book_id int
}
fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"}) - 切片(重点)
- for range:在切片中返回元素索引和索引对应的值,在集合中返回key-value对
- Map是一种无序的键值对的集合,使用hash实现,可惜像数组和切片一样迭代
1
2/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type - 语言接口:把所有具有共性的方法定义在一起,任何其他类型只要是实现了这些方法就是实现了这个接口
2021/5/31
Go环境配置
windows直接下载msi文件,Linux直接用安装好go的docker。
使用go get、go install(vscode安装go工具包)、go mod(代码运行)命令是,会下载相应的包或依赖包,但由于一些原因,从golang.org/x/...
下载包会失败。从Go 1.11
版本起,官方支持了go module
包依赖管理工具和GOPROXY
环境变量,可以通过更改代理来解决下载失败问题(Linux下用export命令): 1
2go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct
Go单元测试
- 使用Go语言的标准库
testing
进行单元测试,测试文件以_test.go
结尾 - 测试用例名称一般命名为
Test
加上代测试的方法名 - 运行
go test
,默认执行当前目录下以xxx_test.go
的测试文件,该package下所有测试用例都会被执行,使用参数-v
会显示每个用例的测试结果 - 如果只想运行其中的一个用例,可以使用参数
-run
指定,该参数支持通配符和部分正则表达式