Vearch源码阅读——ps

358 阅读5分钟

上一篇文章讲了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)

分别看一下NewServerStart两个函数吧

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,如果这个partitionReplicas和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下面这几个成员变量

  1. ctx context.Context
  2. changeLeaderC chan *changeLeaderEntry
  3. replicasStatusC chan *raftstore.ReplicasStatusEntry

context是golang中用来解决协程之间退出通知元数据传递的功能。

image.png 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的过程里看看