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 завершает всю реализацию регистрации.
Готов к работе
- Нужен консул, можно найти в консулеОфициальный сайтЗагрузите бинарный исполняемый файл консула, вы можете использовать команду напрямую
./consul agent -dev -client 0.0.0.0 -ui
включить консула - Отдача ссылается на пример в Go-Micro DOC. настраивать
MICRO_REGISTRY=consul
переменные среды, а затем запустите демо. - Я использую версию 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 мы узнали о деталях его внутренней реализации. Обычно, когда мы хотим просмотреть исходный код, мы должны сначала понять структуру проекта в целом, которую можно узнать по имени документа или пакета. Поняв структуру проекта, мы можем сосредоточиться на интересующем его подкомпоненте, а затем углубиться в исходный код. Прочитайте код со своими вопросами, затем идите искать ответ, изучите некоторые методы написания кода и, наконец, запишите то, что вы узнали.Я думаю, что в этом смысл изучения и чтения исходного кода.