Анализ исходного кода go-micro: Реестр

Go

go-microСодержит основные требования для разработки распределенных систем, включая RPC и механизмы связи, управляемые событиями. Подробнее о go-micro см. в проекте go-micro на git В этой статье в основном обсуждается анализ исходного кода реестра компонентов go-micro.

Структурная схема go-micro выглядит следующим образом (исходный репозиторий git).

Рисунок 1.1

Видно, что нижний слой go-micro разделен на 6 компонентов, а именно брокер, кодек, регистр, селектор и транспорт. Реестр — это модуль регистрации go-micro.Он предоставляет подключаемые функции регистрации и обнаружения сервисов.Его текущие методы реализации включают Consul, etcd, memory и k8s. Давайте возьмем консул в качестве примера, чтобы увидеть, как go-micro завершает всю реализацию регистрации.

Готов к работе

  1. Нужен консул, можно найти в консулеОфициальный сайтЗагрузите бинарный исполняемый файл консула, вы можете использовать команду напрямую./consul agent -dev -client 0.0.0.0 -uiвключить консула
  2. Отдача ссылается на пример в Go-Micro DOC. настраиватьMICRO_REGISTRY=consulпеременные среды, а затем запустите демо.
  3. Я использую версию goland для Mac, которая удобна для отслеживания исходного кода, вы можете использовать goland илиdelveАналогичные инструменты для отладки.

Анализ кода

Регистрация услуги реализована на сервере go-micro, демо-версия услуги находится вservice.Run(), которая является точкой входа для запуска самой внешней службы.RunРеализация находится в сервисе.го. Перейдите к методу Run, чтобы найти(s *service) Start().

func (s *service) Start() error {
	for _, fn := range s.opts.BeforeStart {
		if err := fn(); err != nil {
			return err
		}
	}

	if err := s.opts.Server.Start(); err != nil {
		return err
	}

	for _, fn := range s.opts.AfterStart {
		if err := fn(); err != nil {
			return err
		}
	}

	return nil
}

Этот метод последовательно выполняется внутри, и выполняются три процесса, включая обработку событий перед запуском сервера, запуск службы и обработку события окончания службы. Основной код находится вs.opts.Server.Start(). Код трассировки входит в эту функцию запуска и входит в(s *rpcServer) Start()Внутри находится основной код службы в go-micro. Количество строк кода относительно велико, и мы сосредоточимся непосредственно на функции регистра. оказатьсяRegisterКод для раздела регистрации.

func (s *rpcServer) Start() error {
    ...
    // use RegisterCheck func before register
	if err = s.opts.RegisterCheck(s.opts.Context); err != nil {
		log.Logf("Server %s-%s register check error: %s", config.Name, config.Id, err)
	} else {
		// announce self to the world
		if err = s.Register(); err != nil {
			log.Log("Server %s-%s register error: %s", config.Name, config.Id, err)
		}
	}
	...
}

这里,首先检查了register环境的上下文。如果没有问题则进行注册操作。 Входитьs.Register()в,(s *rpcServer) Register()Эта функция является основной частью кода функции регистрации. Не смотрите на предыдущую предварительную обработку, просто посмотрите на основную часть, которая нас интересует, и найдите следующую строку кода:

func (s *rpcServer) Register() error {
    ...
    if err := config.Registry.Register(service, rOpts...); err != nil {
    	return err
    }
    ...
}    

Нетрудно догадаться, что именно здесь реализована функция регистрации, но откуда go-micro узнает, каким регистратором пользоваться.

Давайте сначала посмотрим на структуру пакета реестра.

Рисунок 1.2

Ссылаясь на структуру каталогов пакета, мы можем знать, что регистратор поддерживает 4 типа операций регистрации, а именно консул, сплетни, mdns и память. мы установилиMICRO_REGISTRY=consulПеременные среды, чтобы указать go-micro использовать consul Register. Так,config.RegistryГде он настроен на консульрегистра?

Ответ в том, что вservice.Init()Он устанавливается при инициализации службы. Вернуться к демонстрации сервиса

// Init will parse the command line flags.
service.Init()

Трассировка в функции Init

func (s *service) Init(opts ...Option) {
	// process options
	for _, o := range opts {
		o(&s.opts)
	}

	s.once.Do(func() {
		// Initialise the command flags, overriding new service
		_ = s.opts.Cmd.Init(
			cmd.Broker(&s.opts.Broker),
			cmd.Registry(&s.opts.Registry),
			cmd.Transport(&s.opts.Transport),
			cmd.Client(&s.opts.Client),
			cmd.Server(&s.opts.Server),
		)
	})
}

В Init цикл for выполняет ряд предварительно обработанных функций. Затем используйте операцию Once в sync.Once, чтобы убедиться, что функция внутри выполняется только один раз. ФокусCmd.InitЭтот метод использует сравнение параметров, которые он получает здесь.

func (c *cmd) Init(opts ...Option) error {
	for _, o := range opts {
		o(&c.opts)
	}
	c.app.Name = c.opts.Name
	c.app.Version = c.opts.Version
	c.app.HideVersion = len(c.opts.Version) == 0
	c.app.Usage = c.opts.Description
	c.app.RunAndExitOnError()
	return nil
}

во-первыхcmd.InitПараметр принимаетtype Option func(o *Options)Затем последовательно выполняются методы этого типа, а затем выполняется присвоение переменных и обработка функций.

Методы, которые он принимает, берут в качестве примера брокера.

func Broker(b *broker.Broker) Option {
	return func(o *Options) {
		o.Broker = b
	}
}

Метод Broker немного сбивает с толку, и его следует рассматривать с двумя предыдущими Init. Здесь используется шаблон декоратора, а Брокер — метод декоратора.

Входитьcmd.InitЗдесь цикл for выполняет методы декоратора по очереди.

Таким образом, его серия операций является S.opts.cmd.opts, чтобы повторно использовать компоненты на сервисе.

Мы продолжаем рассматриватьc.app.RunAndExitOnError()в этом методе, а затем введитеa.Run().

func (a *App) Run(arguments []string) (err error) {
    ...
    if a.Before != nil {
		err = a.Before(context)
		if err != nil {
			fmt.Fprintf(a.Writer, "%v\n\n", err)
			ShowAppHelp(context)
			return err
		}
	}
	...
}

Непосредственно сказать вам, где установить Реестр находится в a.Before здесь. Этот метод не определен сам по себе, а также повторно использует метод cmd.Before. cmd.gonewCmd(opts ...Option)метод, который вы найдетеcmd.app.Before = cmd.Before, устанавливается здесь. Последнее задание в этомcmd.Beforeвнутри.

func (c *cmd) Before(ctx *cli.Context) error {
    ...
    if name := ctx.String("registry"); len(name) > 0 && (*c.opts.Registry).String() != name {
		r, ok := c.opts.Registries[name]
		if !ok {
			return fmt.Errorf("Registry %s not found", name)
		}

		*c.opts.Registry = r()
		serverOpts = append(serverOpts, server.Registry(*c.opts.Registry))
		clientOpts = append(clientOpts, client.Registry(*c.opts.Registry))
		...
	}	
    ...
}

Итак, здесь мы узнаем, это примет стоимость реестра из переменной окружающей среды, настроив консуль, соответствующую консульрию. потом*c.opts.Registry = r()Этот реестр, наконец, настроен здесь.

После долгой тяжелой работы я наконец-то знаю, где взять реестр. Далее вернитесь к предыдущемуRegisterвызов функции там. Идём в консулРеестрRegisterфункция

func (c *consulRegistry) Register(s *registry.Service, opts ...registry.RegisterOption) error {
    ...
    if err := c.Client.Agent().ServiceRegister(asr); err != nil {
		return err
	}
	...
}

Эта функция относительно длинная, и ее основная обработка заключается в общении с консульской службой. существуетAgent().ServiceRegister(asr)Внутри он запустил запрос PUT, конкретный контент запроса может перейти к фактическому контракту.

func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error {
	r := a.c.newRequest("PUT", "/v1/agent/service/register")
	r.obj = service
	_, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

В этот момент go-micro говорит, что сервис зарегистрирован на консуле. В конце концов консул соединит ваш сервер и клиент для связи между ними.

На данный момент мы поняли процесс регистрации сервиса go-micro с точки зрения исходного кода, выбрали регистратора, а затем зарегистрировали сервис, логика других регистраторов такая же и повторяться не будет.

Ниже приведена блок-схема кода, которую я записал.

Рисунок 1.3

Суммировать

Благодаря обзору кода go-micro мы узнали о деталях его внутренней реализации. Обычно, когда мы хотим просмотреть исходный код, мы должны сначала понять структуру проекта в целом, которую можно узнать по имени документа или пакета. Поняв структуру проекта, мы можем сосредоточиться на интересующем его подкомпоненте, а затем углубиться в исходный код. Прочитайте код со своими вопросами, затем идите искать ответ, изучите некоторые методы написания кода и, наконец, запишите то, что вы узнали.Я думаю, что в этом смысл изучения и чтения исходного кода.