Go实践微服务 -- go-micro编写微服务
在之前介绍了《Go 实践微服务 – gRPC 配置和使用》,通过 gRPC 直接编写微服务有点麻烦,比如都需要手动指定服务地址和端口,不便于管理,也需要自己来实现服务注册和发现的逻辑,同时对于网关以及监控等都是微服务架构和实施中不可避免的。所以引入 go-micro,go-micro 是一个插件式的 RPC 框架,它是Micro生态的一部分,Micro 是一个简化分布式开发的微服务生态系统,它为开发分布式应用程序提供了基本的构建模块。
Go Micro 抽象出分布式系统的细节。以下是主要功能。
- 服务发现 - 通过服务发现自动注册和名称解析
- 负载平衡 - 基于发现构建的服务的智能客户端负载平衡
- 同步通信 - 基于 RPC 的通信,支持双向流
- 异步通信 - 为事件驱动架构内置的 Pub/Sub 接口
- 消息编码 - 基于带有 protobuf 和 json 的内容类型的动态编码
- 服务接口 - 所有功能都打包在一个简单的高级界面中,用于开发微服务
通过写一个船运的示例看下 go-micro 如何来写一个微服务。
安装 protoc-gen-micro
go get github.com/micro/protoc-gen-micro
编写 proto 文件来定义服务
consignment-service/proto/consignment/consignment.proto
syntax = "proto3";
package go.micro.srv.consignment;
// 定义货轮服务
service ShippingService {
// 托运一批货物
rpc CreateConsignment (Consignment) returns (Response) {
}
// 查看货物信息
rpc GetConsignments (GetRequest) returns (Response) {
}
}
// 货物信息
message Consignment {
string id = 1; // ID
string description = 2; // 描述
int32 weight = 3; // 重量
repeated Container containers = 4; // 集装箱,多个
string vessel_id = 5; // 承运货轮ID
}
// 集装箱
message Container {
string id = 1; // 编号
string customer_id = 2; // 客户ID
string origin = 3; // 出发地
string user_id = 4; // 集装箱所属用户ID
}
// 托运结果
message Response {
bool created = 1; // 托运结果
Consignment consignment = 2; // 新托运的货物
repeated Consignment consignments = 3; // 所有托运的货物
}
message GetRequest {
}
生成 gRPC 代码
consignment-service/Makefile
build:
# 主要是这里的plugin换成micro,不是grpc
protoc -I. --go_out=plugins=micro:$(GOPATH)/src/shippy/consignment-service proto/consignment/consignment.proto
GOOS=linux GOARCH=amd64 go build
docker build -t consignment-service .
run:
docker run -p 50051:50051 -e MICRO_SERVER_ADDRESS=:50051 -e MICRO_REGISTRY=mdns consignment-service
后面执行make build
之后会生成consignment-service/proto/consignment/consignment.pb.go文件,代码中定义了服务以及服务端和客户端的接口
编写服务端代码
consignment-service/main.go
package main
import (
pb "shippy/consignment-service/proto/consignment"
"context"
"log"
"github.com/micro/go-micro"
)
type IRepository interface {
Create(consignment *pb.Consignment) (*pb.Consignment, error)
GetAll() []*pb.Consignment
}
type Repository struct {
consignments []*pb.Consignment
}
func (repo *Repository) Create(consignment *pb.Consignment) (*pb.Consignment, error) {
repo.consignments = append(repo.consignments, consignment)
return consignment, nil
}
func (repo *Repository) GetAll() []*pb.Consignment {
return repo.consignments
}
// 定义服务
type service struct {
repo Repository
}
// 实现托运接口
func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment, resp *pb.Response) (error) {
consignment, err := s.repo.Create(req)
if err != nil {
return err
}
res.Created = true
res.Consignment = consignment
return nil
}
// 实现获取所有托运接口
func (s *service) GetConsignments(ctx context.Context, req *pb.GetRequest, resp *pb.Response) error {
allConsignments := s.repo.GetAll()
resp = &pb.Response{Consignments: allConsignments}
return nil
}
func main() {
//新建返回服务
server := micro.NewService(
micro.Name("go.micro.srv.consignment"), // 必须和consignment.proto中的package一致
micro.Version("latest"),
)
// 初始化,它会调用call.Init解析命令行参数
server.Init()
repo := Repository{}
// server作为微服务的服务端
pb.RegisterShippingServiceHandler(server.Server(), &service{repo})
if err := server.Run(); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
服务端运行 Docker
服务端运行在 docker 中,编写 Dockerfile consignment-service/Dockerfile
FROM alpine:latest
RUN mkdir /app
WORKDIR /app
ADD consignment-service /app/consignment-service
CMD ["./consignment-service"]
客户端测试数据
{
"description": "This is a test consignment",
"weight": 550,
"containers": [
{
"customer_id": "cust001",
"user_id": "user001",
"origin": "Manchester, United Kingdom"
}
],
"vessel_id": "vessel001"
}
RPC 客户端
consignment-cli/cli.go
package main
import (
pb "shippy/consignment-service/proto/consignment"
"io/ioutil"
"encoding/json"
"errors"
"log"
"os"
"context"
"github.com/micro/go-micro"
)
const (
DEFAULT_INFO_FILE = "consignment.json"
)
func parseFile(fileName string) (*pb.Consignment, error) {
data, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, err
}
var consignment *pb.Consignment
err = json.Unmarshal(data, &consignment)
if err != nil {
return nil, errors.New("consignment.json file content error")
}
return consignment, nil
}
func main() {
service := micro.NewService(micro.Name("go.micro.srv.consignment"))
service.Init()
client := pb.NewShippingServiceClient("go.micro.srv.consignment", service.Client())
infoFile := DEFAULT_INFO_FILE
if len(os.Args) > 1 {
infoFile = os.Args[1]
}
consignment, err := parseFile(infoFile)
if err != nil {
log.Fatalf("parse info file error: %v", err)
}
// 调用 RPC
// 将货物存储到我们自己的仓库里
resp, err := client.CreateConsignment(context.Background(), consignment)
if err != nil {
log.Fatalf("create consignment error: %v", err)
}
// 新货物是否托运成功
log.Printf("created: %t", resp.Created)
log.Printf("resp: %v", resp)
// 列出目前所有托运的货物
resp, err = client.GetConsignments(context.Background(), &pb.GetRequest{})
if err != nil {
log.Fatalf("failed to list consignments: %v", err)
}
for _, c := range resp.Consignments {
log.Printf("%+v", c)
}
}
客户端运行 Docker
客户端也运行在 docker 中,编写 Dockerfile 文件 consignment-cli/Dockerfile
FROM alpine:latest
RUN mkdir -p /app
WORKDIR /app
ADD consignment.json /app/consignment.json
ADD consignment-cli /app/consignment-cli
CMD ["./consignment-cli"]
客户端 Makefile
consignment-cli/Makefile
build:
GOOS=linux GOARCH=amd64 go build
docker build -t consignment-cli .
run:
docker run -e MICRO_REGISTRY=mdns consignment-cli
在 Dockerfile 看到运行设置的环境变量 MICRO_REGISTRY=mdns
, 这样设置 go-micro 在本地使用 mdns
多播作为服务发现的中间层。后续的会单独写文章介绍 Consul 来做服务发现。
这样用 go-micro 来写 RPC 的一个例子就写完了,来运行一下。
运行服务端
Ryan@Ryan-MacPro:~/go/src/shippy/consignment-service|
⇒ make build
protoc -I. --go_out=plugins=micro:/Users/Ryan/go/src/shippy/consignment-service proto/consignment/consignment.proto
GOOS=linux GOARCH=amd64 go build
docker build -t consignment-service .
Sending build context to Docker daemon 13.76MB
Step 1/5 : FROM alpine:latest
---> 3fd9065eaf02
Step 2/5 : RUN mkdir /app
---> Using cache
---> 6a016c2db290
Step 3/5 : WORKDIR /app
---> Using cache
---> 1a3342a37011
Step 4/5 : ADD consignment-service /app/consignment-service
---> 12ec7a999492
Step 5/5 : CMD ["./consignment-service"]
---> Running in 2fe8f35ea587
Removing intermediate container 2fe8f35ea587
---> 134e42ade520
Successfully built 134e42ade520
Successfully tagged consignment-service:latest
Ryan@Ryan-MacPro:~/go/src/shippy/consignment-service|
⇒ make run
docker run -p 50051:50051 -e MICRO_SERVER_ADDRESS=:50051 -e MICRO_REGISTRY=mdns consignment-service
2018/06/20 03:23:31 Listening on [::]:50051
2018/06/20 03:23:31 Broker Listening on [::]:45565
2018/06/20 03:23:31 Registering node: go.micro.srv.consignmen-4ec21b30-7439-11e8-9eac-0242ac110002
运行客户端
Ryan@Ryan-MacPro:~/go/src/shippy/consignment-cli|
⇒ make build && make run
GOOS=linux GOARCH=amd64 go build
docker build -t consignment-cli .
Sending build context to Docker daemon 13.75MB
Step 1/6 : FROM alpine:latest
---> 3fd9065eaf02
Step 2/6 : RUN mkdir -p /app
---> Using cache
---> 6f2a5b3e898a
Step 3/6 : WORKDIR /app
---> Using cache
---> 270191111caf
Step 4/6 : ADD consignment.json /app/consignment.json
---> Using cache
---> 1257da9b9557
Step 5/6 : ADD consignment-cli /app/consignment-cli
---> Using cache
---> d7082fdd340c
Step 6/6 : CMD ["./consignment-cli"]
---> Using cache
---> c02efbcee963
Successfully built c02efbcee963
Successfully tagged consignment-cli:latest
docker run -e MICRO_REGISTRY=mdns consignment-cli
2018/06/21 12:20:00 created: true
2018/06/21 12:20:00 resp: created:true consignment:<description:"This is a test consignment" weight:55000 containers:<customer_id:"cust001" origin:"Manchester, United Kingdom" user_id:"user001" > containers:<customer_id:"cust002" origin:"Derby, United Kingdom" user_id:"user001" > containers:<customer_id:"cust005" origin:"Sheffield, United Kingdom" user_id:"user001" > >
2018/06/21 12:20:00 description:"This is a test consignment" weight:55000 containers:<customer_id:"cust001" origin:"Manchester, United Kingdom" user_id:"user001" > containers:<customer_id:"cust002" origin:"Derby, United Kingdom" user_id:"user001" > containers:<customer_id:"cust005" origin:"Sheffield, United Kingdom" user_id:"user001" >
可以从客户端答应的信息中看出调用成功返回结果。
转载请注明: 转载自Ryan 是菜鸟 | LNMP 技术栈笔记
如果觉得本篇文章对您十分有益,何不 打赏一下
本文链接地址: Go 实践微服务 – go-micro 编写微服务
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可