[ПЕРЕВОД] Учебное пособие на микросервис GoLang (5)

Микросервисы Go

Оригинальная ссылка:ewanvalentine.io, перевод одобрен авторомEwan Valentine уполномоченный.

Полный код этой статьи:GitHub

В предыдущем разделе мы использовали JWT для аутентификации пользователей между микросервисами. В этом разделе мы будем использовать go-micro в сочетании с плагином nats для публикации и подписки на события, созданные пользователями.

Как упоминалось в предыдущих разделах, go-micro — это фреймворк подключаемого типа, способный стыковаться с большим количеством хорошего программного обеспечения с открытым исходным кодом, см. список подключаемых модулей:go-plugins, вы можете видеть, что многие превосходные компоненты были поддержаны.

управляемый событиями

концепция

событийно-ориентированная архитектураЭто относительно просто понять.Принято считать, что хорошие программные архитектуры не связаны между собой, а микросервисы не должны быть связаны или зависеть друг от друга. В качестве примера, мы вызываем микросервис в нашем кодеgo.srv.user-serviceФункция сначала найдет адрес микросервиса через обнаружение сервиса, а затем вызовет его.В нашем коде есть прямое взаимодействие вызова с микросервисом, что не является полной развязкой. Больше ссылок:Начало работы с программной архитектурой

Опубликовать и подписаться

Чтобы понять, почему архитектура, управляемая событиями, может полностью отделить код, сначала разберитесь с процессом публикации и подписки на события. После того, как микросервис X завершает задачу x, он уведомляет систему сообщений о том, что «x завершено», ему все равно, какие микросервисы прослушивают это событие и какое влияние оно окажет после того, как событие произойдет. Если в системе происходит какое-либо событие, другие микросервисы могут легко принять меры.

Например, служба пользователя создает нового пользователя, служба электронной почты должна отправить пользователю электронное письмо об успешной регистрации, а служба сообщений должна отправить администратору веб-сайта SMS-уведомление о регистрации пользователя.

Общая реализация

После создания экземпляров двух других клиентов микрослужбы в коде пользовательской службы и вызова функции для отправки электронных и текстовых сообщений связь кода становится очень высокой. Как показано ниже:

image-20180529201004910

управляемый событиями

В управляемой событиями архитектуре пользовательская служба только что выпустила тему как «user.created» сообщение в систему сообщений, две другие службы, подписанные на эту тему, могут знать регистрацию пользователя, пользователи получают информацию, которую они сами отправляют по электронной почте. , отправлять текстовые сообщения. Как показано ниже:

image-20180529200406346

В этом разделе мы опубликуем событие, когда пользовательская служба создает нового пользователя, в результате чего служба электронной почты отправляет пользователю электронное письмо.

Код

Плагин go-micro NATS

Мы сначалаNATSПлагин интегрирован в наш код:

// user-service/main.go

func main() {
	...
	// 初始化命令行环境
	srv.Init()
    
	// 获取 broker 实例
	pubSub := srv.Server().Options().Broker
	
	// 注册 handler
	pb.RegisterUserServiceHandler(srv.Server(), &handler{repo, &t, pubSub})
	...
}

Важно отметить, что когда go-micro создает микросервисы,srv.Init()Будут загружены все конфигурации микросервиса, такие как используемые плагины, заданные переменные среды, параметры командной строки и т. д. Эти элементы конфигурации будут выполняться как часть микросервиса. быть пригодным для использованияs.Server().Options()чтобы получить эти конфигурации.

Устанавливаем в MakefileGO_MICRO_BROKERПеременная среды go-micro будет использовать систему сообщений NATS, указанную этим адресом, для подписки и публикации событий.

Опубликовать событие

Публикуем событие при создании нового пользователя, смотрите полный код:GitHub

// user-service/handler.go

const topic = "user.created"

type handler struct {
	repo         Repository
	tokenService Authable
	PubSub       broker.Broker
}

func (h *handler) Create(ctx context.Context, req *pb.User, resp *pb.Response) error {
	// 哈希处理用户输入的密码
	hashedPwd, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	req.Password = string(hashedPwd)
	if err := h.repo.Create(req); err != nil {
		return nil
	}
	resp.User = req

	// 发布带有用户所有信息的消息
	if err := h.publishEvent(req); err != nil {
		return err
	}
	return nil
}

// 发送消息通知
func (h *handler) publishEvent(user *pb.User) error {
	body, err := json.Marshal(user)
	if err != nil {
		return err
	}

	msg := &broker.Message{
		Header: map[string]string{
			"id": user.Id,
		},
		Body: body,
	}

	// 发布 user.created topic 消息
	if err := h.PubSub.Publish(topic, msg); err != nil {
		log.Fatalf("[pub] failed: %v\n", err)
	}
	return nil
}

...

Перед запуском убедитесь, что ваш контейнер Postgres работает правильно:

$ docker run -d -p 5432:5432 postgres
$ make build
$ make run

Подписаться на подписку на события

Теперь создайте новый почтовый сервис:email-service, он будет уведомлен по электронной почте, когда будет создан новый пользователь.

package main

import (
	userPb "shippy/user-service/proto/user"
	"github.com/micro/go-micro"
	"log"
	"github.com/micro/go-micro/broker"
	_ "github.com/micro/go-plugins/broker/nats"
	"encoding/json"
)

const topic = "user.created"

func main() {
	srv := micro.NewService(
		micro.Name("go.micro.srv.email"),
		micro.Version("latest"),
	)
	srv.Init()

	pubSub := srv.Server().Options().Broker
	if err := pubSub.Connect(); err != nil {
		log.Fatalf("broker connect error: %v\n", err)
	}

	// 订阅消息
	_, err := pubSub.Subscribe(topic, func(pub broker.Publication) error {
		var user *userPb.User
		if err := json.Unmarshal(pub.Message().Body, &user); err != nil {
			return err
		}
		log.Printf("[Create User]: %v\n", user)
		go senEmail(user)
		return nil
	})

	if err != nil {
		log.Printf("sub error: %v\n", err)
	}

	if err := srv.Run(); err != nil {
		log.Fatalf("srv run error: %v\n", err)
	}
}

func senEmail(user *userPb.User) error {
	log.Printf("[SENDING A EMAIL TO %s...]", user.Name)
	return nil
}

Перед запуском почтовой службы убедитесь, что NATS запущен:

$ docker run -d -p 4222:4222 nats

Теперь запустите и службу пользователя, и службу электронной почты, используйте user-cli для создания нового пользователя Ewan, и вы увидите, что электронное письмо было отправлено пользователю Ewan:

5.1

Переключить плагин брокера сообщений

Стоит отметить, что потребление производительности NATS на основе JSON будет выше, чем у gRPC, и требуется дополнительная обработка строк JSON, которая подходит только для нескольких сценариев приложений. go-micro также поддерживает многие широко используемые технологии очереди сообщений/публикации и подписки, см.:Список плагинов агента сообщенияПоскольку Go-Micro абстракция, между ними очень легко переключаться. Например, вы хотите изменить NATS в GooglePubsub:

// 修改容器的环境变量
// MICRO_BROKER=nats
MICRO_BROKER=googlepubsub	

// 修改 user-service 导入的包
// _ "github.com/micro/go-plugins/broker/nats"
_"github.com/micro/go-plugins/broker/googlepubsub"

Если вы не используете go-micro, вы можете использовать реализацию GoNATS, событие публикуется:

nc, _ := nats.Connect(nats.DefaultURL)

// Simple Publisher
nc.Publish("user.created", userJsonString)

Подписка на мероприятие:

// Simple Async Subscriber
nc.Subscribe("user.created", func(m *nats.Msg) {
    user := convertUserString(m.Data)
    go sendEmail(user)
})

Использование подключаемого модуля стороннего брокера сообщений, такого как NATS, приведет к тому, что микросервисы потеряют преимущество использования protobuf для обмена двоичными данными (без прямых вызовов между микросервисами), но увеличат накладные расходы на обработку данных JSON, на это есть контрмеры.

Pubsub слой

Go-Micro имеет встроенный слой Pubsub, который расположен в верхней части прокси-слоя и не требует стороннего брокера сообщений NATS. Он соответствующим образом использует наш определенный ProTobuf и обновляет службу пользователя для использования PubSub вместо из Натс:

// user-service/main.go

func main() {
    ... 
    publisher := micro.NewPublisher(topic, srv.Client())
	pb.RegisterUserServiceHandler(srv.Server(), &service{repo, tokenService, publisher})
    ... 
}
// user-service/handler.go

func (h *handler) Create(ctx context.Context, req *pb.User, resp *pb.Response) error {
	// 哈希处理用户输入的密码
	hashedPwd, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	req.Password = string(hashedPwd)
	if err := h.repo.Create(req); err != nil {
		return nil
	}
	resp.User = req

	// 发布带有用户所有信息的消息
	if err := h.Publisher.Publish(ctx, req); err != nil {
		return err
	}
	return nil
}

Обновите почтовый микросервис:

// email-service/main.go

type Subscriber struct{}

func main() {
    ...
    micro.RegisterSubscriber(topic, srv.Server(), new(Subscriber))
    ...
}

func (sub *Subscriber) Process(ctx context.Context, user *userPb.User) error {
	log.Println("[Picked up a new message]")
	log.Println("[Sending email to]:", user.Name)
	return nil
}

Теперь нижний уровень нашего микросервиса использует определение User Protobuf и не использует сторонний брокер сообщений. Эффект операции следующий:

5.2

Суммировать

В этом разделе сначала используется подключаемый модуль брокера сообщений NATS от go-micro, так что пользовательская служба публикует событие сообщения с информацией о пользователе и темой «user.created» при создании нового пользователя, а служба электронной почты, подписанная на эту тему, получает После сообщения информация о пользователе извлекается для отправки почты. После этого уровень pubsub, поставляемый с go-micro, используется вместо NATS, чтобы в полной мере использовать преимущества связи protobuf.

В следующем разделе мы будем использовать React для написания интерфейса управления микросервисом и изучения того, как веб-сторона взаимодействует с микросервисом.