Оригинальная ссылка:ewanvalentine.io, перевод одобрен авторомEwan Valentine уполномоченный.
Полный код этой статьи:GitHub
В предыдущем разделе мы использовали JWT для аутентификации пользователей между микросервисами. В этом разделе мы будем использовать go-micro в сочетании с плагином nats для публикации и подписки на события, созданные пользователями.
Как упоминалось в предыдущих разделах, go-micro — это фреймворк подключаемого типа, способный стыковаться с большим количеством хорошего программного обеспечения с открытым исходным кодом, см. список подключаемых модулей:go-plugins, вы можете видеть, что многие превосходные компоненты были поддержаны.
управляемый событиями
концепция
событийно-ориентированная архитектураЭто относительно просто понять.Принято считать, что хорошие программные архитектуры не связаны между собой, а микросервисы не должны быть связаны или зависеть друг от друга. В качестве примера, мы вызываем микросервис в нашем кодеgo.srv.user-service
Функция сначала найдет адрес микросервиса через обнаружение сервиса, а затем вызовет его.В нашем коде есть прямое взаимодействие вызова с микросервисом, что не является полной развязкой. Больше ссылок:Начало работы с программной архитектурой
Опубликовать и подписаться
Чтобы понять, почему архитектура, управляемая событиями, может полностью отделить код, сначала разберитесь с процессом публикации и подписки на события. После того, как микросервис X завершает задачу x, он уведомляет систему сообщений о том, что «x завершено», ему все равно, какие микросервисы прослушивают это событие и какое влияние оно окажет после того, как событие произойдет. Если в системе происходит какое-либо событие, другие микросервисы могут легко принять меры.
Например, служба пользователя создает нового пользователя, служба электронной почты должна отправить пользователю электронное письмо об успешной регистрации, а служба сообщений должна отправить администратору веб-сайта SMS-уведомление о регистрации пользователя.
Общая реализация
После создания экземпляров двух других клиентов микрослужбы в коде пользовательской службы и вызова функции для отправки электронных и текстовых сообщений связь кода становится очень высокой. Как показано ниже:
управляемый событиями
В управляемой событиями архитектуре пользовательская служба только что выпустила тему как «user.created» сообщение в систему сообщений, две другие службы, подписанные на эту тему, могут знать регистрацию пользователя, пользователи получают информацию, которую они сами отправляют по электронной почте. , отправлять текстовые сообщения. Как показано ниже:
В этом разделе мы опубликуем событие, когда пользовательская служба создает нового пользователя, в результате чего служба электронной почты отправляет пользователю электронное письмо.
Код
Плагин 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:
Переключить плагин брокера сообщений
Стоит отметить, что потребление производительности 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 и не использует сторонний брокер сообщений. Эффект операции следующий:
Суммировать
В этом разделе сначала используется подключаемый модуль брокера сообщений NATS от go-micro, так что пользовательская служба публикует событие сообщения с информацией о пользователе и темой «user.created» при создании нового пользователя, а служба электронной почты, подписанная на эту тему, получает После сообщения информация о пользователе извлекается для отправки почты. После этого уровень pubsub, поставляемый с go-micro, используется вместо NATS, чтобы в полной мере использовать преимущества связи protobuf.
В следующем разделе мы будем использовать React для написания интерфейса управления микросервисом и изучения того, как веб-сторона взаимодействует с микросервисом.