milvus启动源码分析

milvus启动源码分析

版本:v2.3.2

入口:

shell 复制代码
cmd\main.go

代码如下:

go 复制代码
func main() {
	......
	if idx > 0 {
		......
	} else {
        // 重点分析这里
		milvus.RunMilvus(os.Args)
	}
}

os.Args是一个[]string类型。

shell 复制代码
bin/milvus run standalone

os.Args[0]是程序的路径,后面的元素则是传递给程序的参数。

os.Args[1]:run

os.Args[2]:standalone

RunMilvus

shell 复制代码
cmd\milvus\milvus.go

milvus.RunMilvus(os.Args)相当于是个壳子,一个简洁的程序入口。

go 复制代码
func RunMilvus(args []string) {
	if len(args) < 2 {
		fmt.Fprintln(os.Stderr, usageLine)
		return
	}
	cmd := args[1]
	flags := flag.NewFlagSet(args[0], flag.ExitOnError)
	flags.Usage = func() {
		fmt.Fprintln(os.Stderr, usageLine)
	}

	var c command
	switch cmd {
	case RunCmd:
		c = &run{}
	case StopCmd:
		c = &stop{}
	case DryRunCmd:
		c = &dryRun{}
	case MckCmd:
		c = &mck{}
	default:
		c = &defaultCommand{}
	}

	c.execute(args, flags)
}

在这里使用了flag包,实现了命令行参数解析。

command是一个接口,只有一个方法execute()。

go 复制代码
type command interface {
	execute(args []string, flags *flag.FlagSet)
}

这个方法有2个入参:args和flags。

command有5个实现:

run、stop、mck、dryRun、defaultCommand

switch判断给变量c赋予什么命令实现,这里是run。

这个接口设计是程序可以传入run、stop、mck、dryRun等参数,每种命令都为了实现一定功能,因此抽象一个execute()执行方法。

args作为execute()的参数。run后面还有其它组件参数(standalone、querycoord等)。

execute

分析run命令的execute()方法。

go 复制代码
func (c *run) execute(args []string, flags *flag.FlagSet) {
	......
	//这里serverType的值为standalone
	serverType := args[2]
    // roles是结构体MilvusRoles
	roles := GetMilvusRoles(args, flags)
	......
	roles.Run()
}

GetMilvusRoles()返回结构体类型MilvusRoles。用来决定milvus启动什么组件。

go 复制代码
// MilvusRoles decides which components are brought up with Milvus.
type MilvusRoles struct {
	EnableRootCoord  bool `env:"ENABLE_ROOT_COORD"`
	EnableProxy      bool `env:"ENABLE_PROXY"`
	EnableQueryCoord bool `env:"ENABLE_QUERY_COORD"`
	EnableQueryNode  bool `env:"ENABLE_QUERY_NODE"`
	EnableDataCoord  bool `env:"ENABLE_DATA_COORD"`
	EnableDataNode   bool `env:"ENABLE_DATA_NODE"`
	EnableIndexCoord bool `env:"ENABLE_INDEX_COORD"`
	EnableIndexNode  bool `env:"ENABLE_INDEX_NODE"`

	Local    bool
	Alias    string
	Embedded bool

	closed chan struct{}
	once   sync.Once
}

roles.Run()启动milvus组件。根据roles里面变量的值来判断启动什么组件。

Run

shell 复制代码
cmd\roles\roles.go

源码如下:

go 复制代码
// Run Milvus components.
func (mr *MilvusRoles) Run() {
	......

	// only standalone enable localMsg
    // standalone模式
	if mr.Local {
		if err := os.Setenv(metricsinfo.DeployModeEnvKey, metricsinfo.StandaloneDeployMode); err != nil {
			log.Error("Failed to set deploy mode: ", zap.Error(err))
		}

		if mr.Embedded {
			......
		} else {
            // 读取配置文件参数进行初始化赋值
			paramtable.Init()
		}
        // 配置都存储在变量params,是一个结构体类型ComponentParam
		params := paramtable.Get()
		if params.EtcdCfg.UseEmbedEtcd.GetAsBool() {
			......
		}
		paramtable.SetRole(typeutil.StandaloneRole)
	} else {
		......
	}

	......

	var rootCoord, queryCoord, indexCoord, dataCoord component
	var proxy, dataNode, indexNode, queryNode component
	if mr.EnableRootCoord {
		rootCoord = mr.runRootCoord(ctx, local, &wg)
	}

	if mr.EnableProxy {
		proxy = mr.runProxy(ctx, local, &wg)
	}

	if mr.EnableQueryCoord {
		queryCoord = mr.runQueryCoord(ctx, local, &wg)
	}

	if mr.EnableQueryNode {
		queryNode = mr.runQueryNode(ctx, local, &wg)
	}

	if mr.EnableDataCoord {
		dataCoord = mr.runDataCoord(ctx, local, &wg)
	}

	if mr.EnableDataNode {
		dataNode = mr.runDataNode(ctx, local, &wg)
	}

	if mr.EnableIndexCoord {
		indexCoord = mr.runIndexCoord(ctx, local, &wg)
	}

	if mr.EnableIndexNode {
		indexNode = mr.runIndexNode(ctx, local, &wg)
	}

	wg.Wait()
	......
}

component是一个接口,有如下实现:

DataCoord、DataNode、IndexCoord、IndexNode、Proxy、QueryCoord、QueryNode、RootCoord

go 复制代码
type component interface {
	healthz.Indicator
	Run() error
	Stop() error
}

组件有启动Run(()和停止Stop()方法。

以启动rootCoord为例:

go 复制代码
rootCoord = mr.runRootCoord(ctx, local, &wg)
go 复制代码
func (mr *MilvusRoles) runRootCoord(ctx context.Context, localMsg bool, wg *sync.WaitGroup) component {
	wg.Add(1)
	return runComponent(ctx, localMsg, wg, components.NewRootCoord, metrics.RegisterRootCoord)
}

runComponent是一个通用函数,做了一个包裹,用来启动各组件。

go 复制代码
func runComponent[T component](ctx context.Context,
	localMsg bool,
	runWg *sync.WaitGroup,
	creator func(context.Context, dependency.Factory) (T, error),
	metricRegister func(*prometheus.Registry),
) component {
    // role在这里就是组件
	var role T

	sign := make(chan struct{})
    // 在协程里面启动组件
	go func() {
		factory := dependency.NewFactory(localMsg)
		var err error
        // 返回的是一个component的具体实现。
		role, err = creator(ctx, factory)
		......
        // 启动component
		if err := role.Run(); err != nil {
			panic(err)
		}
		runWg.Done()
	}()
    ......
	return role
}

components.NewRootCoords是一个函数,函数作为入参,替换creator。

总体启动流程设计分析

命令格式:

shell 复制代码
(base) root@db01:/mnt/milvus# bin/milvus
Usage:

milvus run [server type] [flags]
	Start a Milvus Server.
	Tips: Only the server type is 'mixture', flags about starting server can be used.
[flags]
	-rootcoord 'true'
		Start the rootcoord server.
	-querycoord 'true'
		Start the querycoord server.
	-indexcoord 'true'
		Start the indexcoord server.
	-datacoord 'true'
		Start the datacoord server.
	-alias ''
		Set alias


milvus stop [server type] [flags]
	Stop a Milvus Server.
[flags]
	-alias ''
		Set alias


milvus mck run [flags]
	Milvus data consistency check.
	Tips: The flags are optional.
[flags]
	-etcdIp ''
		Ip to connect the ectd server.
	-etcdRootPath ''
		The root path of operating the etcd data.
	-minioAddress ''
		Address to connect the minio server.
	-minioUsername ''
		The username to login the minio server.
	-minioPassword ''
		The password to login the minio server.
	-minioUseSSL 'false'
		Whether to use the ssl to connect the minio server.
	-minioBucketName ''
		The bucket to operate the data in it

milvus mck cleanTrash [flags]
	Clean the back inconsistent data
	Tips: The flags is the same as its of the 'milvus mck [flags]'


[server type]
	indexnode
	datacoord
	datanode
	standalone
	rootcoord
	proxy
	querycoord
	querynode
	mixture

**1.**根据这个命令格式来设计代码。

bin/milvus [run|stop|mck]

通过Args[1]判断执行的是哪种命令。使用switch实现。

每种命令都有execute()方法来具体执行逻辑。因此抽象出一个接口command,接口有一个execute()方法。

command的具体实现就有run、stop、mck等。

**2.**run命令后面接[server type],分别启动不同的组件类型。

Args[2]:[server type]

这些组件可以抽象出一个接口component,接口有方法Run()、Stop(),用来启动和停止组件。

serverType参数从命令行传入进来需要如何处理?

常规的思路就是判断serverType是哪一种组件,然后启动相应组件。伪代码如下:

go 复制代码
switch serverType {
   case "rootcoord":
    启动rootcoord()
   case "datacoord":
    启动datacoord()
   case "querycoord":
    启动querycoord()
   case "standalone":
    启动rootcoord()
    启动datacoord()
    启动querycoord()
   ......
}

这样设计会发现有组件启动冗余的代码。那如何设计才能不写重复代码?milvus是如何设计的。

milvus设计了一个结构体用来记录启动哪个组件。

go 复制代码
// MilvusRoles decides which components are brought up with Milvus.
type MilvusRoles struct {
	EnableRootCoord  bool `env:"ENABLE_ROOT_COORD"`
	EnableProxy      bool `env:"ENABLE_PROXY"`
	EnableQueryCoord bool `env:"ENABLE_QUERY_COORD"`
	EnableQueryNode  bool `env:"ENABLE_QUERY_NODE"`
	EnableDataCoord  bool `env:"ENABLE_DATA_COORD"`
	EnableDataNode   bool `env:"ENABLE_DATA_NODE"`
	EnableIndexCoord bool `env:"ENABLE_INDEX_COORD"`
	EnableIndexNode  bool `env:"ENABLE_INDEX_NODE"`
    ......
}

根据传入的serverType参数来填充这个结构体。

go 复制代码
switch serverType {
	case typeutil.RootCoordRole:
		role.EnableRootCoord = true
	case typeutil.ProxyRole:
		role.EnableProxy = true
	case typeutil.QueryCoordRole:
		role.EnableQueryCoord = true
	case typeutil.QueryNodeRole:
		role.EnableQueryNode = true
	case typeutil.DataCoordRole:
		role.EnableDataCoord = true
	case typeutil.DataNodeRole:
		role.EnableDataNode = true
	case typeutil.IndexCoordRole:
		role.EnableIndexCoord = true
	case typeutil.IndexNodeRole:
		role.EnableIndexNode = true
	case typeutil.StandaloneRole, typeutil.EmbeddedRole:
		role.EnableRootCoord = true
		role.EnableProxy = true
		role.EnableQueryCoord = true
		role.EnableQueryNode = true
		role.EnableDataCoord = true
		role.EnableDataNode = true
		role.EnableIndexCoord = true
		role.EnableIndexNode = true
		role.Local = true
		role.Embedded = serverType == typeutil.EmbeddedRole
	case RoleMixture:
		......
	default:
		......
	}

把多次启动代码转化为变量赋值重复的代码,相对来说代码结构变清晰了。

go 复制代码
var rootCoord, queryCoord, indexCoord, dataCoord component
	var proxy, dataNode, indexNode, queryNode component
	if mr.EnableRootCoord {
		rootCoord = mr.runRootCoord(ctx, local, &wg)
	}

	if mr.EnableProxy {
		proxy = mr.runProxy(ctx, local, &wg)
	}

	if mr.EnableQueryCoord {
		queryCoord = mr.runQueryCoord(ctx, local, &wg)
	}

	if mr.EnableQueryNode {
		queryNode = mr.runQueryNode(ctx, local, &wg)
	}

	if mr.EnableDataCoord {
		dataCoord = mr.runDataCoord(ctx, local, &wg)
	}

	if mr.EnableDataNode {
		dataNode = mr.runDataNode(ctx, local, &wg)
	}

	if mr.EnableIndexCoord {
		indexCoord = mr.runIndexCoord(ctx, local, &wg)
	}

	if mr.EnableIndexNode {
		indexNode = mr.runIndexNode(ctx, local, &wg)
	}

**3.**每个组件都要启动,可以抽取一个方法runComponent()用来启动不同的组件。

go 复制代码
func (mr *MilvusRoles) runRootCoord(ctx context.Context, localMsg bool, wg *sync.WaitGroup) component {
	wg.Add(1)
	return runComponent(ctx, localMsg, wg, components.NewRootCoord, metrics.RegisterRootCoord)
}

NewRootCoord是一个函数,用来创建rootCoord结构体。在runComponent里调用结构体的Run()方法。

堆栈:

go 复制代码
milvus.RunMilvus()(cmd\main.go)
  |--c.execute()(cmd\milvus\milvus.go)
    |--execute()(cmd\milvus\run.go)
      |--roles.Run()(cmd\roles\roles.go)
        |--mr.runRootCoord()(同上)
          |--runComponent()(同上)
            |--role.Run(同上)
              |--Run()(comd\components\root_coord.go)

从堆栈可以很清晰的看出调用逻辑。

入口是main.go,然后调用milvus的run指令,然后调用roles(组件的抽象成层),最终调用components。

package调用方向:main->milvus->roles->components

相关推荐
渣哥9 小时前
原来 Java 里线程安全集合有这么多种
java
间彧9 小时前
Spring Boot集成Spring Security完整指南
java
间彧10 小时前
Spring Secutiy基本原理及工作流程
java
Java水解11 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆13 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学13 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole13 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊13 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端
程序员鱼皮14 小时前
刚刚 Java 25 炸裂发布!让 Java 再次伟大
java·javascript·计算机·程序员·编程·开发·代码