go 教程:使用 go-380玩彩网官网入口
在上一篇文章中,我们使用 kit 命令行工具搭建了一个本地环境。在本文中我们将继续使用这些代码。
让我们首先通过编写原型定义来实现 notificator 服务,它是一个 grpc 服务。我们已经生成了 notificator/pkg/grpc/pb/notificator.pb
文件,让我们简单的去实现它。
syntax = "proto3";
package pb;
service notificator {
rpc sendemail (sendemailrequest) returns (sendemailreply);
}
message sendemailrequest {
string email = 1;
string content = 2;
}
message sendemailreply {
string id = 1;
}
现在我们要生成服务端和客户端的存根,可以使用 kit 工具生成的 compile.sh
脚本,脚本已经包含 protoc
命令。
cd notificator/pkg/grpc/pb
./compile.sh
可以发现,notificator.pb.go
这个文件已经更新了。
现在我们需要实现服务本身了。我们用生成一个 uuid 代替发送真实的电子邮件。首先需要对服务进行调整以匹配我们的请求和响应格式(返回一个新的参数:id
)。
notificator/pkg/service/service.go:
import (
"context"
uuid "github.com/satori/go.uuid"
)
// notificatorservice describes the service.
type notificatorservice interface {
// add your methods here
sendemail(ctx context.context, email string, content string) (string, error)
}
type basicnotificatorservice struct{}
func (b *basicnotificatorservice) sendemail(ctx context.context, email string, content string) (string, error) {
id, err := uuid.newv4()
if err != nil {
return "", err
}
return id.string(), nil
}
notificator/pkg/service/middleware.go:
func (l loggingmiddleware) sendemail(ctx context.context, email string, content string) (string, error) {
defer func() {
l.logger.log("method", "sendemail", "email", email, "content", content)
}()
return l.next.sendemail(ctx, email, content)
}
notificator/pkg/endpoint/endpoint.go
// sendemailresponse 接受 sendemail 方法的响应
type sendemailresponse struct {
id string
e0 error `json:"e0"`
}
// makesendemailendpoint 返回 sendemail 调用的端点
func makesendemailendpoint(s service.notificatorservice) endpoint.endpoint {
return func(ctx context.context, request interface{}) (interface{}, error) {
req := request.(sendemailrequest)
id, e0 := s.sendemail(ctx, req.email, req.content)
return sendemailresponse{id: id, e0: e0}, nil
}
}
如果我们搜索 todo grep -r "todo" notificator
,可以看到还需要实现 grpc 请求和响应的编码和解码。
notificator/pkg/grpc/handler.go:
func decodesendemailrequest(_ context.context, r interface{}) (interface{}, error) {
req := r.(*pb.sendemailrequest)
return endpoint.sendemailrequest{email: req.email, content: req.content}, nil
}
func encodesendemailresponse(_ context.context, r interface{}) (interface{}, error) {
reply := r.(endpoint.sendemailresponse)
return &pb.sendemailreply{id: reply.id}, nil
}
服务发现
sendemail 接口将由用户服务调用,所以用户服务必须知道通知服务的地址,这是典型的服务发现问题。在本地环境中,我们使用 docker compose 部署时知道如何连接服务,但是,在实际的分布式环境中,可能会遇到其他问题。
首先,在 etcd 中注册我们的通知服务。etcd 是一种可靠的分布式键值存储,广泛应用于服务发现。go-kit 支持使用其他的服务发现技术如:eureka、consul 和 zookeeper 等。
把它添加进 docker compose 中,这样380玩彩网官网入口的服务就可以使用它了。cv 工程师上线:
docker-compose.yml:
etcd:
image: 'quay.io/coreos/etcd:v3.1.7'
restart: always
ports:
- '23791:2379'
- '23801:2380'
environment:
etcd_name: infra
etcd_initial_advertise_peer_urls: 'http://etcd:2380'
etcd_initial_cluster: infra=http://etcd:2380
etcd_initial_cluster_state: new
etcd_initial_cluster_token: secrettoken
etcd_listen_client_urls: 'http://etcd:2379,http://localhost:2379'
etcd_listen_peer_urls: 'http://etcd:2380'
etcd_advertise_client_urls: 'http://etcd:2379'
在 etcd 中注册通知服务 notificator/cmd/service/service.go:
registrar, err := registerservice(logger)
if err != nil {
logger.log(err)
return
}
defer registrar.deregister()
func registerservice(logger log.logger) (*sdetcd.registrar, error) {
var (
etcdserver = "http://etcd:2379"
prefix = "/services/notificator/"
instance = "notificator:8082"
key = prefix instance
)
client, err := sdetcd.newclient(context.background(), []string{etcdserver}, sdetcd.clientoptions{})
if err != nil {
return nil, err
}
registrar := sdetcd.newregistrar(client, sdetcd.service{
key: key,
value: instance,
}, logger)
registrar.register()
return registrar, nil
}
当我们的程序停止或者崩溃时,应该要记得取消注册。现在 etcd 已经知道380玩彩网官网入口的服务了,这个例子中只有一个实例,实际环境中显然会有更多。
现在让我们来测试一下通知服务是否注册到了 etcd 中:
docker-compose up -d etcd
docker-compose up -d notificator
现在我们使用用户服务调用通知服务,当我们创建一个服务后它会发送一个虚构的通知给用户。
由于通知服务是一个 grpc 服务,在用户服务中,我们需要和客户端共用一个客户端存根。
protobuf 的客户端存根代码在 notificator/pkg/grpc/pb/notificator.pb.go
,我们把这个包引入我们的客户端
users/pkg/service/service.go:
import (
"github.com/plutov/packagemain/13-go-kit-2/notificator/pkg/grpc/pb"
"google.golang.org/grpc"
)
type basicusersservice struct {
notificatorserviceclient pb.notificatorclient
}
func (b *basicusersservice) create(ctx context.context, email string) error {
reply, err := b.notificatorserviceclient.sendemail(context.background(), &pb.sendemailrequest{
email: email,
content: "hi! thank you for registration...",
})
if reply != nil {
log.printf("email id: %s", reply.id)
}
return err
}
// newbasicusersservice 返回一个简单的、无状态的用户服务
func newbasicusersservice() usersservice {
conn, err := grpc.dial("notificator:8082", grpc.withinsecure())
if err != nil {
log.printf("unable to connect to notificator: %s", err.error())
return new(basicusersservice)
}
log.printf("connected to notificator")
return &basicusersservice{
notificatorserviceclient: pb.newnotificatorclient(conn),
}
}
当通知服务注册到 etcd 中时,我们可以用 etcd 的地址替换它的硬编码地址。
var etcdserver = "http://etcd:2379"
client, err := sdetcd.newclient(context.background(), []string{etcdserver}, sdetcd.clientoptions{})
if err != nil {
log.printf("unable to connect to etcd: %s", err.error())
return new(basicusersservice)
}
entries, err := client.getentries("/services/notificator/")
if err != nil || len(entries) == 0 {
log.printf("unable to get prefix entries: %s", err.error())
return new(basicusersservice)
}
conn, err := grpc.dial(entries[0], grpc.withinsecure())
因为我们只有一个服务,所以只获取到了一个连接,但实际系统中可能有上百个服务,所以我们可以应用一些逻辑来进行实例选择,例如轮询。
现在让我们启动用户服务来测试一下:
docker-compose up users
调用 http 接口来创建一个用户:
curl -xpost http://localhost:8802/create -d '{"email": "test"}'
结论
这篇文章我们实现了虚构的 notificator grpc 服务,将它注册到 etcd 中,并在用户服务中调用它。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 cc 协议,如果我们的工作有侵犯到您的权益,请及时联系380玩彩网官网入口。
原文地址:
etcd注册通知服务那块需要导什么包啊