上一篇文章讲了vearch的部署,这篇我们来看看ps,从启动开始看,希望看完能回答下面这些问题
- ps怎么决定挂在哪个router下的
- ps下的partition是怎么分配的
启动
启动的命令如下:
nohup $BasePath/vearch -conf $BasePath/config.toml ps
vearch启动的入口在startup.go
下的main
函数,不过先通过init()函数解析参数
func init() {
flag.StringVar(&confPath, "conf", getDefaultConfigFile(), "vearch config path")
flag.StringVar(&masterName, "master", "", "vearch config for master name , is on local start two master must use it")
}
tips:go语言执行main函数前会执行init()函数
这里参数confPath指的是conf.toml文件,内容如下
[global]
name = "vearch"
# 数组
data = ["/home/vearch/Data/baud/datas/"]
log = "/home/vearch/Data/baud/logs/"
level = "debug"
signkey = "vearch"
skip_auth = true
[[masters]]
name = "master1"
address = "ip1"
api_port = 8817
etcd_port = 2378
etcd_peer_port = 2390
etcd_client_port = 2370
skip_auth = true
[[masters]]
name = "master2"
address = "ip2"
api_port = 8817
etcd_port = 2378
etcd_peer_port = 2390
etcd_client_port = 2370
skip_auth = true
[[masters]]
name = "master3"
address = "ip3"
api_port = 8817
etcd_port = 2378
etcd_peer_port = 2390
etcd_client_port = 2370
skip_auth = true
[router]
# port for server
port = 9001
# skip auth for client visit data
skip_auth = true
[ps]
# port for server
rpc_port = 8081
# raft config begin
raft_heartbeat_port = 8898
raft_replicate_port = 8899
heartbeat-interval = 200 #ms
raft_retain_logs = 10000
raft_replica_concurrency = 1
raft_snap_concurrency = 1
配置大多根据命名就很明白,其他内容用到再说
启动ps主要就下面几句
server := ps.NewServer(ctx)
server.Start()
http.ListenAndServe("0.0.0.0:"+cast.ToString(port)
分别看一下NewServer
和Start
两个函数吧
NewServer()
func NewServer(ctx context.Context) *Server {
cli, err := client.NewClient(config.Conf())
changeLeaderC := make(chan *changeLeaderEntry, 1000)
replicasStatusC := make(chan *raftstore.ReplicasStatusEntry, 1000)
s := &Server{
client: cli,
raftResolver: raftstore.NewRaftResolver(),
changeLeaderC: changeLeaderC,
replicasStatusC: replicasStatusC,
}
s.concurrentNum = defaultConcurrentNum
if config.Conf().PS.ConcurrentNum > 0 {
s.concurrentNum = config.Conf().PS.ConcurrentNum
}
s.concurrent = make(chan bool, s.concurrentNum)
s.rpcTimeOut = defaultRpcTimeOut
if config.Conf().PS.RpcTimeOut > 0 {
s.rpcTimeOut = config.Conf().PS.RpcTimeOut
}
s.ctx, s.ctxCancel = context.WithCancel(ctx)
s.rpcServer = rpc.NewRpcServer(config.LocalCastAddr, config.Conf().PS.RpcPort) // any port ???
return s
}
这个函数就是根据配置设置 ps Server
的一些属性,回答不了我们的问题,没啥仔细看的价值
Start()
func (s *Server) Start() error {
s.stopping.Set(false)
// load meta data
nodeId := psutil.InitMeta(s.client, config.Conf().Global.Name, config.Conf().GetDataDir())
s.nodeID = nodeId
if config.Conf().Global.MergeRouter {
// get router ips
s.getRouterIPS(s.ctx)
}
//load local partitions
server := s.register()
s.ip = server.Ip
mserver.SetIp(server.Ip, true)
// create raft server
s.raftServer, err = raftstore.StartRaftServer(nodeId, s.ip, s.raftResolver)
// create and recover partitions
s.recoverPartitions(server.PartitionIds)
//change leader job start
s.startChangeLeaderC()
//heartbeat job start
s.StartHeartbeatJob()
//start rpc server
s.rpcServer.Run(); err != nil {
ExportToRpcHandler(s)
ExportToRpcAdminHandler(s)
return nil
}
计算nodeID
第一阶段算出了一个NodeID
,这个每个ps一个,来看看怎么算的
func InitMeta(client *client.Client, cluster, dataPath string) entity.NodeID {
var nodeID entity.NodeID
metaPath := filepath.Join(dataPath, MetaFile)
_, err := os.Stat(metaPath)
if err != nil {
if os.IsNotExist(err) {
log.Info("Server create meta to file is: %v", metaPath)
nodeID = createMeta(client, cluster, metaPath)
} else {
panic(err)
}
} else {
log.Info("Server load meta from file is: %v", metaPath)
nodeID = readMeta(cluster, metaPath)
}
return nodeID
}
当没有meta文件时,调用createMeta
函数创建一个nodeID,最后是通过下面这句算的nodeID
client.Master().NewIDGenerate(
context.Background(),
entity.NodeIdSequence, // "/id/node"
1,
3*time.Second
)
这里理解是:在etcd以/id/node
为key记录了最新的nodeID,如果是空的,NewIDGenerate
返回base, 也就是第一次返回1。如果非空,则自增1
InitMeta
函数最后把nodeID写入文件
把ps注册到master上
server := s.register()
s.ip = server.Ip //自己的ip
mserver.SetIp(server.Ip, true)
func (s *Server) register() (server *entity.Server) {
var err error
server, err = s.client.Master().Register(
s.ctx,
config.Conf().Global.Name,
s.nodeID,
30*time.Second)
return server
}
ps通过上面这段函数发送一个http请求给master把自己注册到master上,master接收到的是一个"/register"
方法,处理的函数是master_http_api.go
下面的register
函数,再调用registerServerService()
函数
// registerServerService find nodeId partitions
func (ms *masterService) registerServerService(ctx context.Context, ip string, nodeID entity.NodeID) (*entity.Server, error) {
server := &entity.Server{Ip: ip}
spaces, err := ms.Master().QuerySpacesByKey(ctx, entity.PrefixSpace)
if err != nil {
return nil, err
}
for _, s := range spaces {
for _, p := range s.Partitions {
for _, id := range p.Replicas {
if nodeID == id {
server.PartitionIds = append(server.PartitionIds, p.Id)
break
}
}
}
}
return server, nil
}
看代码,所谓注册其实没改变master的什么,而是遍历master存储的所有spaces
下的所有partition
,如果这个partition
的Replicas
和ps的nodeID
相同,就把该partition的ID加入ps的PartitionIds
数组,最后请求返回改完的数组给ps,ps通过返回的server.PartitionIds[]
就知道哪些partition
是它负责的了!根据这个猜测,确定哪些机器负责哪些partition应该再master启动的流程中
那么注册访问的是哪个master呢?
答:从配置里maaster列表下标为0开始,失败就+1往后试
mserver定义在
MetricServer.go
,还不太清楚是干嘛的
启动raft Server
然后是启动raftServer, raft相关后续学习学习
create and recover partitions
上上步register
中,master返回了一个server,ps通过server.PartitionIds来创建对应的partition,调用的函数是LoadPartition(pid)
s.recoverPartitions(server.PartitionIds)
func (s *Server) recoverPartitions(pids []entity.PartitionID) {
ctx := context.Background()
for i := 0; i < len(pids); i++ {
go func(pid entity.PartitionID) {
_, err := s.LoadPartition(ctx, pid)
}(pids[i])
}
}
看看LoadPartition
具体做了啥
func (s *Server) LoadPartition(ctx context.Context, pid entity.PartitionID) (PartitionStore, error) {
// GetDataDirBySlot返回一个存储数据的路径
// 从 datapath/meta/meta.txt 中读取数据创建一个space,这个文件应该是空的
space, err := psutil.LoadPartitionMeta(config.Conf().GetDataDirBySlot(config.PS, pid), pid)
store, err := raftstore.CreateStore(ctx, pid, s.nodeID, space, s.raftServer, s, s.client)
store.RsStatusC = s.replicasStatusC
replicas := store.GetPartition().Replicas
for _, replica := range replicas {
server, err := s.client.Master().QueryServer(context.Background(), replica); err != nil {
s.raftResolver.AddNode(replica, server.Replica())
}
store.Start()
s.partitions.Store(pid, store)
return store, nil
}
首先调用LoadPartitionMeta()
通过读取 datapath/meta/meta.txt
创建了一个space,
这个函数貌似是master创建partition的时候才会生效,当前,到时候再研究。
大概意思就是master分配好了你要处理哪些partition,把这些生成对应的partition存储在本地
startChangeLeaderC()
这里涉及了ps的Server
下面这几个成员变量
- ctx context.Context
- changeLeaderC chan *changeLeaderEntry
- replicasStatusC chan *raftstore.ReplicasStatusEntry
context是golang中用来解决协程之间退出通知
、元数据传递
的功能。
ctx.Done()
表示被关闭。
changeLeaderC
是一个chan changeLeaderEntry
类型的对象,changeLeaderC<-
就相当于往changeLeaderEntry
里写东西,<-changeLeaderC
相当于从里面取东西.replicasStatusC
也是chan
类型,和changeLeaderC
一个道理
知道这些再看startChangeLeaderC()
函数,其实就是启动一个线程无限循环监控这两个chan
:
- 当
ctx
被关闭时,退出 - 当
changeLeaderC
被写入,执行registerMaster(entry.leader, entry.pid)
- 当
replicasStatusC
被写入,执行s.changeReplicas(pStatus)
StartHeartbeatJob()
根据函数名字应该是启动一个定时任务,我们来看看这个定时任务里都做了些啥吧
一个保持心跳函数,表明还活着,未完待续
问题解答
- ps怎么决定挂在哪个router下的? 没有挂在那个下面之说,
- ps下的partition是怎么分配的? 读ps启动没能解决,去master的启动、以及create partition的过程里看看