在之前介绍了《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编写微服务