精华内容
下载资源
问答
  • Golang 微服务教程

    2020-03-26 23:27:54
    Golang 微服务教程》分为 10 篇,总结微服务开发、测试到部署的完整过程。 本节先介绍微服务的基础概念、术语,再创建我们的第一个微服务 consignment-service 的简洁版。在接下来的第 2~10 节文章中,我们会陆续...

    本节对 gRPC 的使用浅尝辄止,更多可参考:gRPC 中 Client 与 Server 数据交互的 4 种模式

    前言

    系列概览

    《Golang 微服务教程》分为 10 篇,总结微服务开发、测试到部署的完整过程。

    本节先介绍微服务的基础概念、术语,再创建我们的第一个微服务 consignment-service 的简洁版。在接下来的第 2~10 节文章中,我们会陆续创建以下微服务:

    • consignment-service(货运服务)
    • inventory-service(仓库服务)
    • user-service(用户服务)
    • authentication-service(认证服务)
    • role-service (角色服务)
    • vessel-service(货船服务)

    用到的完整技术栈如下:

    Golang, gRPC, go-micro            // 开发语言及其 RPC 框架
    Google Cloud, MongoDB            // 云平台与数据存储
    Docker, Kubernetes, Terrafrom      // 容器化与集群架构
    NATS, CircleCI                    // 消息系统与持续集成

    代码仓库

    作者代码:EwanValentine/shippy,译者的中文注释代码: wuYin/shippy

    每个章节对应仓库的一个分支,比如本文part1 的代码在 feature/part1

    开发环境

    笔者的开发环境为 macOS,本文中使用了 make 工具来高效编译,Windows 用户需 手动安装

    $ go env        
    GOARCH="amd64"    # macOS 环境
    GOOS="darwin"    # 在第二节使用 Docker 构建 alpine 镜像时需修改为 linux
    GOPATH="/Users/wuyin/Go"
    GOROOT="/usr/local/go"

    准备

    掌握 Golang 的基础语法:推荐阅读谢大的《Go Web 编程》

    安装 gRPC / protobuf

    go get -u google.golang.org/grpc                    # 安装 gRPC 框架
    go get -u github.com/golang/protobuf/protoc-gen-go    # 安装 Go 版本的 protobuf 编译器

    微服务

    我们要写什么项目?

    我们要搭建一个港口的货物管理平台。本项目以微服务的架构开发,整体简单且概念通用。闲话不多说让我们开始微服务之旅吧。

    微服务是什么?

    在传统的软件开发中,整个应用的代码都组织在一个单一的代码库,一般会有以下拆分代码的形式:

    • 按照特征做拆分:如 MVC 模式
    • 按照功能做拆分:在更大的项目中可能会将代码封装在处理不同业务的包中,包内部可能会再做拆分

    不管怎么拆分,最终二者的代码都会集中在一个库中进行开发和管理,可参考:谷歌的单一代码库管理

    微服务是上述第二种拆分方式的拓展,按功能将代码拆分成几个包,都是可独立运行的单一代码库。区别如下:

    image-20180512033801893

    微服务有哪些优势?

    降低复杂性

    将整个应用的代码按功能对应拆分为小且独立的微服务代码库,这不禁让人联想到 Unix 哲学:Do One Thing and Do It Well,在传统单一代码库的应用中,模块之间是紧耦合且边界模糊的,随着产品不断迭代,代码的开发和维护将变得更为复杂,潜在的 bug 和漏洞也会越来越多。

    提高扩展性

    在项目开发中,可能有一部分代码会在多个模块中频繁的被用到,这种复用性很高的模块常常会抽离出来作为公共代码库使用,比如验证模块,当它要扩展功能(添加短信验证码登录等)时,单一代码库的规模只增不减, 整个应用还需重新部署。在微服务架构中,验证模块可作为单个服务独立出来,能独立运行、测试和部署。

    遵循微服务拆分代码的理念,能大大降低模块间的耦合性,横向扩展也会容易许多,正适合当下云计算的高性能、高可用和分布式的开发环境。

    Nginx 有一系列文章来探讨微服务的许多概念,可 点此阅读

    使用 Golang 的好处?

    微服务是一种架构理念而不是具体的框架项目,许多编程语言都可以实现,但有的语言对微服务开发具备天生的优势,Golang 便是其中之一

    Golang 本身十分轻量级,运行效率极高,同时对并发编程有着原生的支持,从而能更好的利用多核处理器。内置 net 标准库对网络开发的支持也十分完善。可参考谢大的短文:Go 语言的优势

    此外,Golang 社区有一个很棒的开源微服务框架 go-mirco,我们在下一节会用到。

    Protobuf 与 gRPC

    在传统应用的单一代码库中,各模块间可直接相互调用函数。但在微服务架构中,由于每个服务对应的代码库是独立运行的,无法直接调用,彼此间的通信就是个大问题,解决方案有 2 个:

    JSON 或 XML 协议的 API

    微服务之间可使用基于 HTTP 的 JSON 或 XML 协议进行通信:服务 A 与服务 B 进行通信前,A 必须把要传递的数据 encode 成 JSON / XML 格式,再以字符串的形式传递给 B,B 接收到数据需要 decode 后才能在代码中使用:

    • 优点:数据易读,使用便捷,是与浏览器交互必选的协议
    • 缺点:在数据量大的情况下 encode、decode 的开销随之变大,多余的字段信息导致传输成本更高

    RPC 协议的 API

    下边的 JSON 数据就使用 descriptionweight 等元数据来描述数据本身的意义,在 Browser / Server 架构中用得很多,以方便浏览器解析:

    {
      "description": "This is a test consignment",
      "weight": 550,
      "containers": [
        {
          "customer_id": "cust001",
          "user_id": "user001",
          "origin": "Manchester, United Kingdom"
        }
      ],
      "vessel_id": "vessel001"
    }

    但在两个微服务之间通信时,若彼此约定好传输数据的格式,可直接使用二进制数据流进行通信,不再需要笨重冗余的元数据。

    gRPC 简介

    gRPC 是谷歌开源的轻量级 RPC 通信框架,其中的通信协议基于二进制数据流,使得 gRPC 具有优异的性能。

    gRPC 支持 HTTP 2.0 协议,使用二进制帧进行数据传输,还可以为通信双方建立持续的双向数据流。可参考:Google HTTP/2 简介

    protobuf 作为通信协议

    两个微服务之间通过基于 HTTP 2.0 二进制数据帧通信,那么如何约定二进制数据的格式呢?答案是使用 gRPC 内置的 protobuf 协议,其 DSL 语法 可清晰定义服务间通信的数据结构。可参考:gRPC Go: Beyond the basics

    consignment-service 微服务开发

    经过上边必要的概念解释,现在让我们开始开发我们的第一个微服务:consignment-service

    项目结构

    假设本项目名为 shippy,你需要:

    • 在 $GOPATH 的 src 目录下新建 shippy 项目目录
    • 在项目目录下新建文件 consignment-service/proto/consignment/consignment.proto

    为便于教学,我会把本项目的所有微服务的代码统一放在 shippy 目录下,这种项目结构被称为 "mono-repo",读者也可以按照 "multi-repo" 将各个微服务拆为独立的项目。更多参考 REPO 风格之争:MONO VS MULTI

    现在你的项目结构应该如下:

    $GOPATH/src
        └── shippy
            └── consignment-service
                └── proto
                    └── consignment
                        └── consignment.proto

    开发流程

    image-20180512044329199

    定义 protobuf 通信协议文件

    // shipper/consignment-service/proto/consignment/consignment.proto
    
    syntax = "proto3";
    package go.micro.srv.consignment;
    
    // 货轮微服务
    service ShippingService {
        // 托运一批货物
        rpc CreateConsignment (Consignment) returns (Response) {
        }
    }
    
    // 货轮承运的一批货物
    message Consignment {
        string id = 1;                      // 货物编号
        string description = 2;             // 货物描述
        int32 weight = 3;                   // 货物重量
        repeated Container containers = 4;  // 这批货有哪些集装箱
        string vessel_id = 5;               // 承运的货轮
    }
    
    // 单个集装箱
    message Container {
        string id = 1;          // 集装箱编号
        string customer_id = 2; // 集装箱所属客户的编号
        string origin = 3;      // 出发地
        string user_id = 4;     // 集装箱所属用户的编号
    }
    
    // 托运结果
    message Response {
        bool created = 1;            // 托运成功
        Consignment consignment = 2;// 新托运的货物
    }

    语法参考: Protobuf doc

    image-20180512010554833

    生成协议代码

    protoc 编译器使用 grpc 插件编译 .proto 文件

    为避免重复的在终端执行编译、运行命令,本项目使用 make 工具,新建 consignment-service/Makefile

    build:
    # 一定要注意 Makefile 中的缩进,否则 make build 可能报错 Nothing to be done for build
    # protoc 命令前边是一个 Tab,不是四个或八个空格
        protoc -I. --go_out=plugins=grpc:$(GOPATH)/src/shippy/consignment-service proto/consignment/consignment.proto

    执行 make build,会在 proto/consignment 目录下生成 consignment.pb.go

    consignment.proto 与 consignment.pb.go 的对应关系

    service:定义了微服务 ShippingService 要暴露为外界调用的函数:CreateConsignment,由 protobuf 编译器的 grpc 插件处理后生成 interface

    type ShippingServiceClient interface {
        // 托运一批货物
        CreateConsignment(ctx context.Context, in *Consignment, opts ...grpc.CallOption) (*Response, error)
    }

    message:定义了通信的数据格式,由 protobuf 编译器处理后生成 struct

    type Consignment struct {
        Id           string       `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
        Description  string       `protobuf:"bytes,2,opt,name=description" json:"description,omitempty"`
        Weight       int32        `protobuf:"varint,3,opt,name=weight" json:"weight,omitempty"`
        Containers   []*Container `protobuf:"bytes,4,rep,name=containers" json:"containers,omitempty"`
        // ...
    }

    实现服务端

    服务端需实现 ShippingServiceClient 接口,创建consignment-service/main.go

    package main
    
    import (
        // 导如 protoc 自动生成的包
        pb "shippy/consignment-service/proto/consignment"
        "context"
        "net"
        "log"
        "google.golang.org/grpc"
    )
    
    const (
        PORT = ":50051"
    )
    
    //
    // 仓库接口
    //
    type IRepository interface {
        Create(consignment *pb.Consignment) (*pb.Consignment, error) // 存放新货物
    }
    
    //
    // 我们存放多批货物的仓库,实现了 IRepository 接口
    //
    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
    }
    
    //
    // service 实现 consignment.pb.go 中的 ShippingServiceServer 接口
    // 使 service 作为 gRPC 的服务端
    //
    // 托运新的货物
    func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment) (*pb.Response, error) {
        // 接收承运的货物
        consignment, err := s.repo.Create(req)
        if err != nil {
            return nil, err
        }
        resp := &pb.Response{Created: true, Consignment: consignment}
        return resp, nil
    }
    
    func main() {
        listener, err := net.Listen("tcp", PORT)
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }
        log.Printf("listen on: %s\n", PORT)
    
        server := grpc.NewServer()
        repo := Repository{}
    
        // 向 rRPC 服务器注册微服务
        // 此时会把我们自己实现的微服务 service 与协议中的 ShippingServiceServer 绑定
        pb.RegisterShippingServiceServer(server, &service{repo})
    
        if err := server.Serve(listener); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }

    上边的代码实现了 consignment-service 微服务所需要的方法,并建立了一个 gRPC 服务器监听 50051 端口。如果你此时运行 go run main.go,将成功启动服务端:

    image-20180512051413002

    实现客户端

    我们将要托运的货物信息放到 consignment-cli/consignment.json

    {
      "description": "This is a test consignment",
      "weight": 550,
      "containers": [
        {
          "customer_id": "cust001",
          "user_id": "user001",
          "origin": "Manchester, United Kingdom"
        }
      ],
      "vessel_id": "vessel001"
    }

    客户端会读取这个 JSON 文件并将该货物托运。在项目目录下新建文件:consingment-cli/cli.go

    package main
    
    import (
        pb "shippy/consignment-service/proto/consignment"
        "io/ioutil"
        "encoding/json"
        "errors"
        "google.golang.org/grpc"
        "log"
        "os"
        "context"
    )
    
    const (
        ADDRESS           = "localhost:50051"
        DEFAULT_INFO_FILE = "consignment.json"
    )
    
    // 读取 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() {
        // 连接到 gRPC 服务器
        conn, err := grpc.Dial(ADDRESS, grpc.WithInsecure())
        if err != nil {
            log.Fatalf("connect error: %v", err)
        }
        defer conn.Close()
    
        // 初始化 gRPC 客户端
        client := pb.NewShippingServiceClient(conn)
    
        // 在命令行中指定新的货物信息 json 文件
        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)
    }

    运行 go run main.go 后再运行 go run cli.go

    grpc-runing

    我们可以新增一个 RPC 查看所有被托运的货物,加入一个GetConsignments方法,这样,我们就能看到所有存在的consignment了:

    // shipper/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;                      // 货物编号
        string description = 2;             // 货物描述
        int32 weight = 3;                   // 货物重量
        repeated Container containers = 4;  // 这批货有哪些集装箱
        string vessel_id = 5;               // 承运的货轮
    }
    
    // 单个集装箱
    message Container {
        string id = 1;          // 集装箱编号
        string customer_id = 2; // 集装箱所属客户的编号
        string origin = 3;      // 出发地
        string user_id = 4;     // 集装箱所属用户的编号
    }
    
    // 托运结果
    message Response {
        bool created = 1;                       // 托运成功
        Consignment consignment = 2;            // 新托运的货物
        repeated Consignment consignments = 3;  // 目前所有托运的货物
    }
    
    // 查看货物信息的请求
    // 客户端想要从服务端请求数据,必须有请求格式,哪怕为空
    message GetRequest {
    }

    现在运行make build来获得最新编译后的微服务界面。如果此时你运行go run main.go,你会获得一个类似这样的错误信息:

    image-20180512020710310

    熟悉Go的你肯定知道,你忘记实现一个interface所需要的方法了。让我们更新consignment-service/main.go:

    package main
    
    import (
        pb "shippy/consignment-service/proto/consignment"
        "context"
        "net"
        "log"
        "google.golang.org/grpc"
    )
    
    const (
        PORT = ":50051"
    )
    
    //
    // 仓库接口
    //
    type IRepository interface {
        Create(consignment *pb.Consignment) (*pb.Consignment, error) // 存放新货物
        GetAll() []*pb.Consignment                                   // 获取仓库中所有的货物
    }
    
    //
    // 我们存放多批货物的仓库,实现了 IRepository 接口
    //
    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
    }
    
    //
    // 实现 consignment.pb.go 中的 ShippingServiceServer 接口
    // 使 service 作为 gRPC 的服务端
    //
    // 托运新的货物
    func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment) (*pb.Response, error) {
        // 接收承运的货物
        consignment, err := s.repo.Create(req)
        if err != nil {
            return nil, err
        }
        resp := &pb.Response{Created: true, Consignment: consignment}
        return resp, nil
    }
    
    // 获取目前所有托运的货物
    func (s *service) GetConsignments(ctx context.Context, req *pb.GetRequest) (*pb.Response, error) {
        allConsignments := s.repo.GetAll()
        resp := &pb.Response{Consignments: allConsignments}
        return resp, nil
    }
    
    func main() {
        listener, err := net.Listen("tcp", PORT)
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }
        log.Printf("listen on: %s\n", PORT)
    
        server := grpc.NewServer()
        repo := Repository{}
        pb.RegisterShippingServiceServer(server, &service{repo})
    
        if err := server.Serve(listener); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }

    如果现在使用go run main.go,一切应该正常:

    image-20180512020218724

    最后让我们更新consignment-cli/cli.go来获得consignment信息:

    func main() {
        ... 
    
        // 列出目前所有托运的货物
        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)
        }
    }

    此时再运行go run cli.go,你应该能看到所创建的所有consignment,多次运行将看到多个货物被托运:

    viewuploading.4e448015.gif转存失败重新上传取消Jietu20180512-053129-HD

    至此,我们使用protobuf和grpc创建了一个微服务以及一个客户端。

    在下一篇文章中,我们将介绍使用go-micro框架,以及创建我们的第二个微服务。同时在下一篇文章中,我们将介绍如何容Docker来容器化我们的微服务。

    展开全文
  • Golang微服务教程

    2019-05-26 16:45:41
    转自:https://segmentfault.com/a/1190000015135650?utm_campaign=studygolang.com&... 前言 系列概览 《Golang 微服务教程》分为 10 篇,总结微服务开发、测试到部署的完整过程。 本节先介绍微服务...

    转自:https://segmentfault.com/a/1190000015135650?utm_campaign=studygolang.com&utm_medium=studygolang.com&utm_source=studygolang.com

    前言

    系列概览

    《Golang 微服务教程》分为 10 篇,总结微服务开发、测试到部署的完整过程。

    本节先介绍微服务的基础概念、术语,再创建我们的第一个微服务 consignment-service 的简洁版。在接下来的第 2~10 节文章中,我们会陆续创建以下微服务:

    • consignment-service(货运服务)
    • inventory-service(仓库服务)
    • user-service(用户服务)
    • authentication-service(认证服务)
    • role-service (角色服务)
    • vessel-service(货船服务)

    用到的完整技术栈如下:

    Golang, gRPC, go-micro            // 开发语言及其 RPC 框架
    Google Cloud, MongoDB            // 云平台与数据存储
    Docker, Kubernetes, Terrafrom      // 容器化与集群架构
    NATS, CircleCI                    // 消息系统与持续集成

    代码仓库

    作者代码:EwanValentine/shippy,译者的中文注释代码: wuYin/shippy

    每个章节对应仓库的一个分支,比如本文part1 的代码在 feature/part1

    开发环境

    笔者的开发环境为 macOS,本文中使用了 make 工具来高效编译,Windows 用户需 手动安装

    $ go env        
    GOARCH="amd64"    # macOS 环境
    GOOS="darwin"    # 在第二节使用 Docker 构建 alpine 镜像时需修改为 linux
    GOPATH="/Users/wuyin/Go"
    GOROOT="/usr/local/go"

    准备

    掌握 Golang 的基础语法:推荐阅读谢大的《Go Web 编程》

    安装 gRPC / protobuf

    go get -u google.golang.org/grpc                    # 安装 gRPC 框架
    go get -u github.com/golang/protobuf/protoc-gen-go    # 安装 Go 版本的 protobuf 编译器

    微服务

    我们要写什么项目?

    我们要搭建一个港口的货物管理平台。本项目以微服务的架构开发,整体简单且概念通用。闲话不多说让我们开始微服务之旅吧。

    微服务是什么?

    在传统的软件开发中,整个应用的代码都组织在一个单一的代码库,一般会有以下拆分代码的形式:

    • 按照特征做拆分:如 MVC 模式
    • 按照功能做拆分:在更大的项目中可能会将代码封装在处理不同业务的包中,包内部可能会再做拆分

    不管怎么拆分,最终二者的代码都会集中在一个库中进行开发和管理,可参考:谷歌的单一代码库管理

    微服务是上述第二种拆分方式的拓展,按功能将代码拆分成几个包,都是可独立运行的单一代码库。区别如下:

    image-20180512033801893

    微服务有哪些优势?

    降低复杂性

    将整个应用的代码按功能对应拆分为小且独立的微服务代码库,这不禁让人联想到 Unix 哲学:Do One Thing and Do It Well,在传统单一代码库的应用中,模块之间是紧耦合且边界模糊的,随着产品不断迭代,代码的开发和维护将变得更为复杂,潜在的 bug 和漏洞也会越来越多。

    提高扩展性

    在项目开发中,可能有一部分代码会在多个模块中频繁的被用到,这种复用性很高的模块常常会抽离出来作为公共代码库使用,比如验证模块,当它要扩展功能(添加短信验证码登录等)时,单一代码库的规模只增不减, 整个应用还需重新部署。在微服务架构中,验证模块可作为单个服务独立出来,能独立运行、测试和部署。

    遵循微服务拆分代码的理念,能大大降低模块间的耦合性,横向扩展也会容易许多,正适合当下云计算的高性能、高可用和分布式的开发环境。

    Nginx 有一系列文章来探讨微服务的许多概念,可 点此阅读

    使用 Golang 的好处?

    微服务是一种架构理念而不是具体的框架项目,许多编程语言都可以实现,但有的语言对微服务开发具备天生的优势,Golang 便是其中之一

    Golang 本身十分轻量级,运行效率极高,同时对并发编程有着原生的支持,从而能更好的利用多核处理器。内置 net 标准库对网络开发的支持也十分完善。可参考谢大的短文:Go 语言的优势

    此外,Golang 社区有一个很棒的开源微服务框架 go-mirco,我们在下一节会用到。

    Protobuf 与 gRPC

    在传统应用的单一代码库中,各模块间可直接相互调用函数。但在微服务架构中,由于每个服务对应的代码库是独立运行的,无法直接调用,彼此间的通信就是个大问题,解决方案有 2 个:

    JSON 或 XML 协议的 API

    微服务之间可使用基于 HTTP 的 JSON 或 XML 协议进行通信:服务 A 与服务 B 进行通信前,A 必须把要传递的数据 encode 成 JSON / XML 格式,再以字符串的形式传递给 B,B 接收到数据需要 decode 后才能在代码中使用:

    • 优点:数据易读,使用便捷,是与浏览器交互必选的协议
    • 缺点:在数据量大的情况下 encode、decode 的开销随之变大,多余的字段信息导致传输成本更高

    RPC 协议的 API

    下边的 JSON 数据就使用 descriptionweight 等元数据来描述数据本身的意义,在 Browser / Server 架构中用得很多,以方便浏览器解析:

    {
      "description": "This is a test consignment",
      "weight": 550,
      "containers": [
        {
          "customer_id": "cust001",
          "user_id": "user001",
          "origin": "Manchester, United Kingdom"
        }
      ],
      "vessel_id": "vessel001"
    }

    但在两个微服务之间通信时,若彼此约定好传输数据的格式,可直接使用二进制数据流进行通信,不再需要笨重冗余的元数据。

    gRPC 简介

    gRPC 是谷歌开源的轻量级 RPC 通信框架,其中的通信协议基于二进制数据流,使得 gRPC 具有优异的性能。

    gRPC 支持 HTTP 2.0 协议,使用二进制帧进行数据传输,还可以为通信双方建立持续的双向数据流。可参考:Google HTTP/2 简介

    protobuf 作为通信协议

    两个微服务之间通过基于 HTTP 2.0 二进制数据帧通信,那么如何约定二进制数据的格式呢?答案是使用 gRPC 内置的 protobuf 协议,其 DSL 语法 可清晰定义服务间通信的数据结构。可参考:gRPC Go: Beyond the basics

    consignment-service 微服务开发

    经过上边必要的概念解释,现在让我们开始开发我们的第一个微服务:consignment-service

    项目结构

    假设本项目名为 shippy,你需要:

    • 在 $GOPATH 的 src 目录下新建 shippy 项目目录
    • 在项目目录下新建文件 consignment-service/proto/consignment/consignment.proto

    为便于教学,我会把本项目的所有微服务的代码统一放在 shippy 目录下,这种项目结构被称为 "mono-repo",读者也可以按照 "multi-repo" 将各个微服务拆为独立的项目。更多参考 REPO 风格之争:MONO VS MULTI

    现在你的项目结构应该如下:

    $GOPATH/src
        └── shippy
            └── consignment-service
                └── proto
                    └── consignment
                        └── consignment.proto

    开发流程

    image-20180512044329199

    定义 protobuf 通信协议文件

    // shipper/consignment-service/proto/consignment/consignment.proto
    
    syntax = "proto3";
    package go.micro.srv.consignment;
    
    // 货轮微服务
    service ShippingService {
        // 托运一批货物
        rpc CreateConsignment (Consignment) returns (Response) {
        }
    }
    
    // 货轮承运的一批货物
    message Consignment {
        string id = 1;                      // 货物编号
        string description = 2;             // 货物描述
        int32 weight = 3;                   // 货物重量
        repeated Container containers = 4;  // 这批货有哪些集装箱
        string vessel_id = 5;               // 承运的货轮
    }
    
    // 单个集装箱
    message Container {
        string id = 1;          // 集装箱编号
        string customer_id = 2; // 集装箱所属客户的编号
        string origin = 3;      // 出发地
        string user_id = 4;     // 集装箱所属用户的编号
    }
    
    // 托运结果
    message Response {
        bool created = 1;            // 托运成功
        Consignment consignment = 2;// 新托运的货物
    }

    语法参考: Protobuf doc

    image-20180512010554833

    生成协议代码

    protoc 编译器使用 grpc 插件编译 .proto 文件

    为避免重复的在终端执行编译、运行命令,本项目使用 make 工具,新建 consignment-service/Makefile

    build:
    # 一定要注意 Makefile 中的缩进,否则 make build 可能报错 Nothing to be done for build
    # protoc 命令前边是一个 Tab,不是四个或八个空格
        protoc -I. --go_out=plugins=grpc:$(GOPATH)/src/shippy/consignment-service proto/consignment/consignment.proto

    执行 make build,会在 proto/consignment 目录下生成 consignment.pb.go

    consignment.proto 与 consignment.pb.go 的对应关系

    service:定义了微服务 ShippingService 要暴露为外界调用的函数:CreateConsignment,由 protobuf 编译器的 grpc 插件处理后生成 interface

    type ShippingServiceClient interface {
        // 托运一批货物
        CreateConsignment(ctx context.Context, in *Consignment, opts ...grpc.CallOption) (*Response, error)
    }

    message:定义了通信的数据格式,由 protobuf 编译器处理后生成 struct

    type Consignment struct {
        Id           string       `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
        Description  string       `protobuf:"bytes,2,opt,name=description" json:"description,omitempty"`
        Weight       int32        `protobuf:"varint,3,opt,name=weight" json:"weight,omitempty"`
        Containers   []*Container `protobuf:"bytes,4,rep,name=containers" json:"containers,omitempty"`
        // ...
    }

    实现服务端

    服务端需实现 ShippingServiceClient 接口,创建consignment-service/main.go

    package main
    
    import (
        // 导如 protoc 自动生成的包
        pb "shippy/consignment-service/proto/consignment"
        "context"
        "net"
        "log"
        "google.golang.org/grpc"
    )
    
    const (
        PORT = ":50051"
    )
    
    //
    // 仓库接口
    //
    type IRepository interface {
        Create(consignment *pb.Consignment) (*pb.Consignment, error) // 存放新货物
    }
    
    //
    // 我们存放多批货物的仓库,实现了 IRepository 接口
    //
    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
    }
    
    //
    // service 实现 consignment.pb.go 中的 ShippingServiceServer 接口
    // 使 service 作为 gRPC 的服务端
    //
    // 托运新的货物
    func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment) (*pb.Response, error) {
        // 接收承运的货物
        consignment, err := s.repo.Create(req)
        if err != nil {
            return nil, err
        }
        resp := &pb.Response{Created: true, Consignment: consignment}
        return resp, nil
    }
    
    func main() {
        listener, err := net.Listen("tcp", PORT)
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }
        log.Printf("listen on: %s\n", PORT)
    
        server := grpc.NewServer()
        repo := Repository{}
    
        // 向 rRPC 服务器注册微服务
        // 此时会把我们自己实现的微服务 service 与协议中的 ShippingServiceServer 绑定
        pb.RegisterShippingServiceServer(server, &service{repo})
    
        if err := server.Serve(listener); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }

    上边的代码实现了 consignment-service 微服务所需要的方法,并建立了一个 gRPC 服务器监听 50051 端口。如果你此时运行 go run main.go,将成功启动服务端:

    image-20180512051413002

    实现客户端

    我们将要托运的货物信息放到 consignment-cli/consignment.json

    {
      "description": "This is a test consignment",
      "weight": 550,
      "containers": [
        {
          "customer_id": "cust001",
          "user_id": "user001",
          "origin": "Manchester, United Kingdom"
        }
      ],
      "vessel_id": "vessel001"
    }

    客户端会读取这个 JSON 文件并将该货物托运。在项目目录下新建文件:consingment-cli/cli.go

    package main
    
    import (
        pb "shippy/consignment-service/proto/consignment"
        "io/ioutil"
        "encoding/json"
        "errors"
        "google.golang.org/grpc"
        "log"
        "os"
        "context"
    )
    
    const (
        ADDRESS           = "localhost:50051"
        DEFAULT_INFO_FILE = "consignment.json"
    )
    
    // 读取 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() {
        // 连接到 gRPC 服务器
        conn, err := grpc.Dial(ADDRESS, grpc.WithInsecure())
        if err != nil {
            log.Fatalf("connect error: %v", err)
        }
        defer conn.Close()
    
        // 初始化 gRPC 客户端
        client := pb.NewShippingServiceClient(conn)
    
        // 在命令行中指定新的货物信息 json 文件
        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)
    }

    运行 go run main.go 后再运行 go run cli.go

    grpc-runing

    我们可以新增一个 RPC 查看所有被托运的货物,加入一个GetConsignments方法,这样,我们就能看到所有存在的consignment了:

    // shipper/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;                      // 货物编号
        string description = 2;             // 货物描述
        int32 weight = 3;                   // 货物重量
        repeated Container containers = 4;  // 这批货有哪些集装箱
        string vessel_id = 5;               // 承运的货轮
    }
    
    // 单个集装箱
    message Container {
        string id = 1;          // 集装箱编号
        string customer_id = 2; // 集装箱所属客户的编号
        string origin = 3;      // 出发地
        string user_id = 4;     // 集装箱所属用户的编号
    }
    
    // 托运结果
    message Response {
        bool created = 1;                       // 托运成功
        Consignment consignment = 2;            // 新托运的货物
        repeated Consignment consignments = 3;  // 目前所有托运的货物
    }
    
    // 查看货物信息的请求
    // 客户端想要从服务端请求数据,必须有请求格式,哪怕为空
    message GetRequest {
    }

    现在运行make build来获得最新编译后的微服务界面。如果此时你运行go run main.go,你会获得一个类似这样的错误信息:

    image-20180512020710310

    熟悉Go的你肯定知道,你忘记实现一个interface所需要的方法了。让我们更新consignment-service/main.go:

    package main
    
    import (
        pb "shippy/consignment-service/proto/consignment"
        "context"
        "net"
        "log"
        "google.golang.org/grpc"
    )
    
    const (
        PORT = ":50051"
    )
    
    //
    // 仓库接口
    //
    type IRepository interface {
        Create(consignment *pb.Consignment) (*pb.Consignment, error) // 存放新货物
        GetAll() []*pb.Consignment                                   // 获取仓库中所有的货物
    }
    
    //
    // 我们存放多批货物的仓库,实现了 IRepository 接口
    //
    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
    }
    
    //
    // 实现 consignment.pb.go 中的 ShippingServiceServer 接口
    // 使 service 作为 gRPC 的服务端
    //
    // 托运新的货物
    func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment) (*pb.Response, error) {
        // 接收承运的货物
        consignment, err := s.repo.Create(req)
        if err != nil {
            return nil, err
        }
        resp := &pb.Response{Created: true, Consignment: consignment}
        return resp, nil
    }
    
    // 获取目前所有托运的货物
    func (s *service) GetConsignments(ctx context.Context, req *pb.GetRequest) (*pb.Response, error) {
        allConsignments := s.repo.GetAll()
        resp := &pb.Response{Consignments: allConsignments}
        return resp, nil
    }
    
    func main() {
        listener, err := net.Listen("tcp", PORT)
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }
        log.Printf("listen on: %s\n", PORT)
    
        server := grpc.NewServer()
        repo := Repository{}
        pb.RegisterShippingServiceServer(server, &service{repo})
    
        if err := server.Serve(listener); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }

    如果现在使用go run main.go,一切应该正常:

    image-20180512020218724

    最后让我们更新consignment-cli/cli.go来获得consignment信息:

    func main() {
        ... 
    
        // 列出目前所有托运的货物
        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)
        }
    }

    此时再运行go run cli.go,你应该能看到所创建的所有consignment,多次运行将看到多个货物被托运:

    Jietu20180512-053129-HD

    至此,我们使用protobuf和grpc创建了一个微服务以及一个客户端。

    展开全文
  • Golang 微服务教程(一)

    千次阅读 2019-01-07 13:59:06
    原文链接:ewanvalentine.io,翻译已获作者 Ewan Valentine ...《Golang 微服务教程》分为 10 篇,总结微服务开发、测试到部署的完整过程。 本节先介绍微服务的基础概念、术语,再创建我们的第一个微服务 consign...

    原文链接:ewanvalentine.io,翻译已获作者 Ewan Valentine 授权。

    本节对 gRPC 的使用浅尝辄止,更多可参考:gRPC 中 Client 与 Server 数据交互的 4 种模式

    前言

    系列概览

    《Golang 微服务教程》分为 10 篇,总结微服务开发、测试到部署的完整过程。

    本节先介绍微服务的基础概念、术语,再创建我们的第一个微服务 consignment-service 的简洁版。在接下来的第 2~10 节文章中,我们会陆续创建以下微服务:

    • consignment-service(货运服务)
    • inventory-service(仓库服务)
    • user-service(用户服务)
    • authentication-service(认证服务)
    • role-service (角色服务)
    • vessel-service(货船服务)

    用到的完整技术栈如下:

    1
    2
    3
    4
    
    Golang, gRPC, go-micro			// 开发语言及其 RPC 框架
    Google Cloud, MongoDB			// 云平台与数据存储
    Docker, Kubernetes, Terrafrom  	// 容器化与集群架构
    NATS, CircleCI					// 消息系统与持续集成
    

    代码仓库

    作者代码:EwanValentine/shippy,译者的中文注释代码: wuYin/shippy

    每个章节对应仓库的一个分支,比如本文part1 的代码在 feature/part1

    开发环境

    笔者的开发环境为 macOS,本文中使用了 make 工具来高效编译,Windows 用户需 手动安装

    1
    2
    3
    4
    5
    
    $ go env		
    GOARCH="amd64"	# macOS 环境
    GOOS="darwin"	# 在第二节使用 Docker 构建 alpine 镜像时需修改为 linux
    GOPATH="/Users/wuyin/Go"
    GOROOT="/usr/local/go"
    

    准备

    掌握 Golang 的基础语法:推荐阅读谢大的《Go Web 编程》

    安装 gRPC / protobuf

    1
    2
    
    go get -u google.golang.org/grpc					# 安装 gRPC 框架
    go get -u github.com/golang/protobuf/protoc-gen-go	# 安装 Go 版本的 protobuf 编译器
    

    微服务

    我们要写什么项目?

    我们要搭建一个港口的货物管理平台。本项目以微服务的架构开发,整体简单且概念通用。闲话不多说让我们开始微服务之旅吧。

    微服务是什么?

    在传统的软件开发中,整个应用的代码都组织在一个单一的代码库,一般会有以下拆分代码的形式:

    • 按照特征做拆分:如 MVC 模式
    • 按照功能做拆分:在更大的项目中可能会将代码封装在处理不同业务的包中,包内部可能会再做拆分

    不管怎么拆分,最终二者的代码都会集中在一个库中进行开发和管理,可参考:谷歌的单一代码库管理

    微服务是上述第二种拆分方式的拓展,按功能将代码拆分成几个包,都是可独立运行的单一代码库。区别如下:

    image-20180512033801893

    微服务有哪些优势?

    降低复杂性

    将整个应用的代码按功能对应拆分为小且独立的微服务代码库,这不禁让人联想到 Unix 哲学:Do One Thing and Do It Well,在传统单一代码库的应用中,模块之间是紧耦合且边界模糊的,随着产品不断迭代,代码的开发和维护将变得更为复杂,潜在的 bug 和漏洞也会越来越多。

    提高扩展性

    在项目开发中,可能有一部分代码会在多个模块中频繁的被用到,这种复用性很高的模块常常会抽离出来作为公共代码库使用,比如验证模块,当它要扩展功能(添加短信验证码登录等)时,单一代码库的规模只增不减, 整个应用还需重新部署。在微服务架构中,验证模块可作为单个服务独立出来,能独立运行、测试和部署。

    遵循微服务拆分代码的理念,能大大降低模块间的耦合性,横向扩展也会容易许多,正适合当下云计算的高性能、高可用和分布式的开发环境。

    Nginx 有一系列文章来探讨微服务的许多概念,可 点此阅读

    使用 Golang 的好处?

    微服务是一种架构理念而不是具体的框架项目,许多编程语言都可以实现,但有的语言对微服务开发具备天生的优势,Golang 便是其中之一

    Golang 本身十分轻量级,运行效率极高,同时对并发编程有着原生的支持,从而能更好的利用多核处理器。内置 net 标准库对网络开发的支持也十分完善。可参考谢大的短文:Go 语言的优势

    此外,Golang 社区有一个很棒的开源微服务框架 go-mirco,我们在下一节会用到。

    Protobuf 与 gRPC

    在传统应用的单一代码库中,各模块间可直接相互调用函数。但在微服务架构中,由于每个服务对应的代码库是独立运行的,无法直接调用,彼此间的通信就是个大问题,解决方案有 2 个:

    JSON 或 XML 协议的 API

    微服务之间可使用基于 HTTP 的 JSON 或 XML 协议进行通信:服务 A 与服务 B 进行通信前,A 必须把要传递的数据 encode 成 JSON / XML 格式,再以字符串的形式传递给 B,B 接收到数据需要 decode 后才能在代码中使用:

    • 优点:数据易读,使用便捷,是与浏览器交互必选的协议
    • 缺点:在数据量大的情况下 encode、decode 的开销随之变大,多余的字段信息导致传输成本更高

    RPC 协议的 API

    下边的 JSON 数据就使用 descriptionweight 等元数据来描述数据本身的意义,在 Browser / Server 架构中用得很多,以方便浏览器解析:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    {
      "description": "This is a test consignment",
      "weight": 550,
      "containers": [
        {
          "customer_id": "cust001",
          "user_id": "user001",
          "origin": "Manchester, United Kingdom"
        }
      ],
      "vessel_id": "vessel001"
    }
    

    但在两个微服务之间通信时,若彼此约定好传输数据的格式,可直接使用二进制数据流进行通信,不再需要笨重冗余的元数据。

    gRPC 简介

    gRPC 是谷歌开源的轻量级 RPC 通信框架,其中的通信协议基于二进制数据流,使得 gRPC 具有优异的性能。

    gRPC 支持 HTTP 2.0 协议,使用二进制帧进行数据传输,还可以为通信双方建立持续的双向数据流。可参考:Google HTTP/2 简介

    protobuf 作为通信协议

    两个微服务之间通过基于 HTTP 2.0 二进制数据帧通信,那么如何约定二进制数据的格式呢?答案是使用 gRPC 内置的 protobuf 协议,其 DSL 语法 可清晰定义服务间通信的数据结构。可参考:gRPC Go: Beyond the basics

    consignment-service 微服务开发

    经过上边必要的概念解释,现在让我们开始开发我们的第一个微服务:consignment-service

    项目结构

    假设本项目名为 shippy,你需要:

    • 在 $GOPATH 的 src 目录下新建 shippy 项目目录
    • 在项目目录下新建文件 consignment-service/proto/consignment/consignment.proto

    为便于教学,我会把本项目的所有微服务的代码统一放在 shippy 目录下,这种项目结构被称为 “mono-repo”,读者也可以按照 “multi-repo” 将各个微服务拆为独立的项目。更多参考 REPO 风格之争:MONO VS MULTI

    现在你的项目结构应该如下:

    1
    2
    3
    4
    5
    6
    
    $GOPATH/src
        └── shippy
            └── consignment-service
                └── proto
                    └── consignment
                        └── consignment.proto
    

    开发流程

    image-20180512044329199

    定义 protobuf 通信协议文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    
    // shipper/consignment-service/proto/consignment/consignment.proto
    
    syntax = "proto3";
    package go.micro.srv.consignment;
    
    // 货轮微服务
    service ShippingService {
        // 托运一批货物
        rpc CreateConsignment (Consignment) returns (Response) {
        }
    }
    
    // 货轮承运的一批货物
    message Consignment {
        string id = 1;                      // 货物编号
        string description = 2;             // 货物描述
        int32 weight = 3;                   // 货物重量
        repeated Container containers = 4;  // 这批货有哪些集装箱
        string vessel_id = 5;               // 承运的货轮
    }
    
    // 单个集装箱
    message Container {
        string id = 1;          // 集装箱编号
        string customer_id = 2; // 集装箱所属客户的编号
        string origin = 3;      // 出发地
        string user_id = 4;     // 集装箱所属用户的编号
    }
    
    // 托运结果
    message Response {
        bool created = 1;			// 托运成功
        Consignment consignment = 2;// 新托运的货物
    }
    

    语法参考: Protobuf doc

    image-20180512010554833

    生成协议代码

    protoc 编译器使用 grpc 插件编译 .proto 文件

    为避免重复的在终端执行编译、运行命令,本项目使用 make 工具,新建 consignment-service/Makefile

    1
    2
    3
    4
    
    build:
    # 一定要注意 Makefile 中的缩进,否则 make build 可能报错 Nothing to be done for build
    # protoc 命令前边是一个 Tab,不是四个或八个空格
    	protoc -I. --go_out=plugins=grpc:$(GOPATH)/src/shippy/consignment-service/proto/consignment/consignment.proto
    

    执行 make build,会在 proto/consignment 目录下生成 consignment.pb.go

    consignment.proto 与 consignment.pb.go 的对应关系

    service:定义了微服务 ShippingService 要暴露为外界调用的函数:CreateConsignment,由 protobuf 编译器的 grpc 插件处理后生成 interface

    1
    2
    3
    4
    
    type ShippingServiceClient interface {
    	// 托运一批货物
    	CreateConsignment(ctx context.Context, in *Consignment, opts ...grpc.CallOption) (*Response, error)
    }
    

    message:定义了通信的数据格式,由 protobuf 编译器处理后生成 struct

    1
    2
    3
    4
    5
    6
    7
    
    type Consignment struct {
    	Id           string       `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
    	Description  string       `protobuf:"bytes,2,opt,name=description" json:"description,omitempty"`
    	Weight       int32        `protobuf:"varint,3,opt,name=weight" json:"weight,omitempty"`
    	Containers   []*Container `protobuf:"bytes,4,rep,name=containers" json:"containers,omitempty"`
        // ...
    }
    

    实现服务端

    服务端需实现 ShippingServiceClient 接口,创建consignment-service/main.go

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    
    package main
    
    import (
        // 导如 protoc 自动生成的包
    	pb "shippy/consignment-service/proto/consignment"
    	"context"
    	"net"
    	"log"
    	"google.golang.org/grpc"
    )
    
    const (
    	PORT = ":50051"
    )
    
    //
    // 仓库接口
    //
    type IRepository interface {
    	Create(consignment *pb.Consignment) (*pb.Consignment, error) // 存放新货物
    }
    
    //
    // 我们存放多批货物的仓库,实现了 IRepository 接口
    //
    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
    }
    
    //
    // service 实现 consignment.pb.go 中的 ShippingServiceServer 接口
    // 使 service 作为 gRPC 的服务端
    //
    // 托运新的货物
    func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment) (*pb.Response, error) {
    	// 接收承运的货物
    	consignment, err := s.repo.Create(req)
    	if err != nil {
    		return nil, err
    	}
    	resp := &pb.Response{Created: true, Consignment: consignment}
    	return resp, nil
    }
    
    func main() {
    	listener, err := net.Listen("tcp", PORT)
    	if err != nil {
    		log.Fatalf("failed to listen: %v", err)
    	}
    	log.Printf("listen on: %s\n", PORT)
    
    	server := grpc.NewServer()
    	repo := Repository{}
    
        // 向 rRPC 服务器注册微服务
        // 此时会把我们自己实现的微服务 service 与协议中的 ShippingServiceServer 绑定
    	pb.RegisterShippingServiceServer(server, &service{repo})
    
    	if err := server.Serve(listener); err != nil {
    		log.Fatalf("failed to serve: %v", err)
    	}
    }
    

    上边的代码实现了 consignment-service 微服务所需要的方法,并建立了一个 gRPC 服务器监听 50051 端口。如果你此时运行 go run main.go,将成功启动服务端:

    image-20180512051413002

    实现客户端

    我们将要托运的货物信息放到 consignment-cli/consignment.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    {
      "description": "This is a test consignment",
      "weight": 550,
      "containers": [
        {
          "customer_id": "cust001",
          "user_id": "user001",
          "origin": "Manchester, United Kingdom"
        }
      ],
      "vessel_id": "vessel001"
    }
    

    客户端会读取这个 JSON 文件并将该货物托运。在项目目录下新建文件:consingment-cli/cli.go

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    
    package main
    
    import (
    	pb "shippy/consignment-service/proto/consignment"
    	"io/ioutil"
    	"encoding/json"
    	"errors"
    	"google.golang.org/grpc"
    	"log"
    	"os"
    	"context"
    )
    
    const (
    	ADDRESS           = "localhost:50051"
    	DEFAULT_INFO_FILE = "consignment.json"
    )
    
    // 读取 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() {
    	// 连接到 gRPC 服务器
    	conn, err := grpc.Dial(ADDRESS, grpc.WithInsecure())
    	if err != nil {
    		log.Fatalf("connect error: %v", err)
    	}
    	defer conn.Close()
    
    	// 初始化 gRPC 客户端
    	client := pb.NewShippingServiceClient(conn)
    
    	// 在命令行中指定新的货物信息 json 文件
    	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)
    }
    

    运行 go run main.go 后再运行 go run cli.go

    grpc-runing

    我们可以新增一个 RPC 查看所有被托运的货物,加入一个GetConsignments方法,这样,我们就能看到所有存在的consignment了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    
    // shipper/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;                      // 货物编号
        string description = 2;             // 货物描述
        int32 weight = 3;                   // 货物重量
        repeated Container containers = 4;  // 这批货有哪些集装箱
        string vessel_id = 5;               // 承运的货轮
    }
    
    // 单个集装箱
    message Container {
        string id = 1;          // 集装箱编号
        string customer_id = 2; // 集装箱所属客户的编号
        string origin = 3;      // 出发地
        string user_id = 4;     // 集装箱所属用户的编号
    }
    
    // 托运结果
    message Response {
        bool created = 1;                       // 托运成功
        Consignment consignment = 2;            // 新托运的货物
        repeated Consignment consignments = 3;  // 目前所有托运的货物
    }
    
    // 查看货物信息的请求
    // 客户端想要从服务端请求数据,必须有请求格式,哪怕为空
    message GetRequest {
    }
    

    现在运行make build来获得最新编译后的微服务界面。如果此时你运行go run main.go,你会获得一个类似这样的错误信息:

    image-20180512020710310

    熟悉Go的你肯定知道,你忘记实现一个interface所需要的方法了。让我们更新consignment-service/main.go:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    
    package main
    
    import (
    	pb "shippy/consignment-service/proto/consignment"
    	"context"
    	"net"
    	"log"
    	"google.golang.org/grpc"
    )
    
    const (
    	PORT = ":50051"
    )
    
    //
    // 仓库接口
    //
    type IRepository interface {
    	Create(consignment *pb.Consignment) (*pb.Consignment, error) // 存放新货物
    	GetAll() []*pb.Consignment                                   // 获取仓库中所有的货物
    }
    
    //
    // 我们存放多批货物的仓库,实现了 IRepository 接口
    //
    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
    }
    
    //
    // 实现 consignment.pb.go 中的 ShippingServiceServer 接口
    // 使 service 作为 gRPC 的服务端
    //
    // 托运新的货物
    func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment) (*pb.Response, error) {
    	// 接收承运的货物
    	consignment, err := s.repo.Create(req)
    	if err != nil {
    		return nil, err
    	}
    	resp := &pb.Response{Created: true, Consignment: consignment}
    	return resp, nil
    }
    
    // 获取目前所有托运的货物
    func (s *service) GetConsignments(ctx context.Context, req *pb.GetRequest) (*pb.Response, error) {
    	allConsignments := s.repo.GetAll()
    	resp := &pb.Response{Consignments: allConsignments}
    	return resp, nil
    }
    
    func main() {
    	listener, err := net.Listen("tcp", PORT)
    	if err != nil {
    		log.Fatalf("failed to listen: %v", err)
    	}
    	log.Printf("listen on: %s\n", PORT)
    
    	server := grpc.NewServer()
    	repo := Repository{}
    	pb.RegisterShippingServiceServer(server, &service{repo})
    
    	if err := server.Serve(listener); err != nil {
    		log.Fatalf("failed to serve: %v", err)
    	}
    }
    

    如果现在使用go run main.go,一切应该正常:

    image-20180512020218724

    最后让我们更新consignment-cli/cli.go来获得consignment信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    func main() {
        ... 
    
    	// 列出目前所有托运的货物
    	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)
    	}
    }
    

    此时再运行go run cli.go,你应该能看到所创建的所有consignment,多次运行将看到多个货物被托运:

    Jietu20180512-053129-HD

    至此,我们使用protobuf和grpc创建了一个微服务以及一个客户端。

    在下一篇文章中,我们将介绍使用go-micro框架,以及创建我们的第二个微服务。同时在下一篇文章中,我们将介绍如何容Docker来容器化我们的微服务。

    参考文献:

    https://wuyin.io/2018/05/10/microservices-part-1-introduction-and-consignment-service/

    展开全文
  • 加入golang的支持1.获取 proto包2.安装protoc-gen-go插件五.demo1.编写test.proto测试类2.编译 一.什么是微服务 据说,早在2011年5月,在威尼斯附近的软件架构师讨论会上,就有人提出了微服务架构设计的概念,用它来...

    一.什么是微服务

    据说,早在2011年5月,在威尼斯附近的软件架构师讨论会上,就有人提出了微服务架构设计的概念,用它来描述与会者所见的一种通用的架构设计风格。时隔一年之后,在同一个讨论会上,大家决定将这种架构设计风格用微服务架构来表示。

    起初,对微服务的概念,没有一个明确的定义,大家只能从各自的角度说出了微服务的理解和看法。
    有人把微服务理解为一种细粒度SOA(service-oriented Architecture,面向服务架构),一种轻量级的组件化的小型SOA。 在2014年3月,詹姆斯·刘易斯(James Lewis)与马丁·福勒(Martin Fowler)所发表的一篇博客中,总结了微服务架构设计的一些共同特点,这应该是一个对微服务比较全面的描述。

    这篇文章中认为:“简而言之,微服务架构风格是将单个应用程序作为一组小型服务开发的方法,每个服务程序都在自己的进程中运行,并与轻量级机制(通常是HTTP资源API)进行通信。这些服务是围绕业务功能构建的。可以通过全自动部署机器独立部署。这些服务器可以用不同的编程语言编写,使用不同的数据存储技术,并尽量不用集中式方式进行管理“

    	原文链接 https://martinfowler.com/articles/microservices.html
    

    二、什么是protobuf

    protobuf是google旗下的一款平台无关,语言无关,可扩展的序列化结构数据格式。
    你可以理解为类似json的传输格式,但是它比json传输更快,体积更小,也更安全。

    protobuf是后起之秀,是谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为profobuf是二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。

    2.1优点

    相对于其它protobuf更具有优势
    1:序列化后体积相比Json和XML很小,适合网络传输
    2:支持跨平台多语言
    3:消息格式升级和兼容性还不错
    4:序列化反序列化速度很快,快于Json的处理速速

    2.2缺点

    Protobuf 与 XML 相比也有不足之处。它功能简单,无法用来表示复杂的概念。
    XML 已经成为多种行业标准的编写工具,Protobuf 只是 Google 公司内部使用的工具,在通用性上还差很多。 由于文本并不适合用来描述数据结构,所以 Protobuf 也不适合用来对基于文本的标记文档(如 HTML)建模。
    另外,由于 XML 具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上 Protobuf 不行,它以二进制的方式存储,除非你有 .proto 定义,否则你没法直接读出 Protobuf 的任何内容。

    三.Protobuf安装步骤

    Linux平台

    #下载 protoBuf: 
    $ git clone https://github.com/protocolbuffers/protobuf.git 123
    #安装依赖库 
    $ sudo apt-get install autoconf automake libtool curl make g++ unzip libffi- dev -y 
    #安装
     $ cd protobuf/ 
     $ ./autogen.sh 
     $ ./configure 
     $ make 
     $ sudo make install $ sudo ldconfig 
     # 刷新共享库 很重要的一步啊 #安装的时候会比较卡 #成功后需要使用命令测试 
     $ protoc –h
    

    windows平台
    1.下载安装文件
    https://github.com/protocolbuffers/protobuf/releases/tag/v3.10.0

    2.解压到任意目录

    3.配置环境变量

    将上一步解压出来的目录/bin(即protoc.exe所在的目录)添加到环境变量中
    

    4.检查

    cmd输入protoc查看是否安装成功
    

    四.加入golang的支持

    1.获取 proto包

    #Go语言的proto API接口
     $ go get -v -u github.com/golang/protobuf/proto
    

    2.安装protoc-gen-go插件

    #安装 
    $ go get -v -u github.com/golang/protobuf/protoc-gen-go
    #编译 
    $ cd $GOPATH/src/github.com/golang/protobuf/protoc-gen-go/ 
    $ go build
    #将生成的 protoc-gen-go可执行文件,放在/bin目录下 
    $ sudo cp protoc-gen-go /bin/
    

    五.demo

    1.编写test.proto测试类

    syntax = "proto3";
    package myproto;
    message Test {
      string name = 1;
      int32 stature = 2 ;
      repeated int64 weight = 3;
      string motto = 4;
    }
    

    2.编译

    进入proto所在目录,执行以下命令即可编译成go文件

    protoc --go_out=./ *.proto
    
    展开全文
  • 简单的说Micro是一个微服务框架 它默认实现了consul作为服务发现,通过http、protobuf、进行通信。 二.Micro安装步骤 1.下载 先把所有的依赖库都下载一遍 go get -u -v github.com/go-log/log go get -u -v github...
  • 译文链接:wuYin/blog原文链接:ewanvalentine...在上节中我们使用 go-micro 搭建了微服务的事件驱动架构。本节将揭晓从 web 客户端的角度出发如何与微服务进行调用交互。 微服务与 web 端交互 参考 go-micro 文档,...
  • 译文链接:wuYin/blog原文链接:ewanvalentine.io,翻译已...在上节中,我们使用 go-micro 重新实现了微服务并进行了 Docker 化,但是每个微服务都要单独维护自己的 Makefile 未免过于繁琐。本节将学习 docker-compo...
  • 译文链接:wuYin/blog原文链接:ewanvalentine...上节引入 user-service 微服务并在 Postgres 中存储了用户数据,包括明文密码。本节将对密码进行安全的加密处理,并使用唯一的 token 来在各微服务之间识别用户。 在...
  • 译文链接:wuYin/blog原文链接:ewanvalentine.io,翻译已获作者 Ewan Valentine 授权。...在上一篇中,我们使用 gRPC 初步实现了我们的微服务,本节将 Docker 化该微服务并引入 go-micro 框架代替 gRPC...
  • golang微服务教程2--GRPC

    2020-07-26 12:22:37
    安装protobuf 可以参考我的上一篇文章golang微服务教程1–protobuf 2.你需要翻墙获取google.golang.org包和golang.org包 如果你能翻墙,请执行 go get -u -v google.golang.org/grpc 否则请执行 进入目录$GOPATH/...
  • 译文链接:wuYin/blog原文链接:...在上节中,我们使用 JWT 在微服务之间进行了用户的认证。在本节中,我们将使用 go-micro 结合 nats 插件来完成用户创建事件的发布与订阅。 正如前几节所说,go-micro ...
  • 目的是跟着github上面的微服务教程走一遍 链接: 构建微服务 第一章 用户服务 第一章中,有个micro new指令,生成模板 micro new --namespace=mu.micro.book --type=srv --alias=user github....
  • golang + docker +领事+ grpc + protobuf + beego + mysql + redis + fastDFS + nginx 目标功能 功能模块 用户模块 注册 获取验证码图片服务 获取短信验证码服务 发送注册信息服务 登录 获取会话信息服务 获取登录...
  • go-micro是基于golang微服务编程框架,go-micro操作简单、编码高效、功能强大。但是网络上资料偏少,本系列文章定位最简单最容易上手的go-micro入门教程,所有案列来自实操,而非网络上的复制粘贴。 本章节的目的...
  • 给大家分享一套课程,Go、Golang、Beego微服务基础实战视频教程,配套资料齐全,欢迎下载学习,给个好评哦
  • Go+Golang+Beego微服务基础实战视频教程-附件资源
  • 一直希望使用golang来搭建微服务。在学习 参考教程 过程中遇到不少坑。现记录在案,方便他人查阅。 一. go get -u google.golang.org/grpc 无法下载,timeout问题 原因:网上两种说法,一种是因为被墙;第二种是这...
  • 微服务教程 微服务最初由Martin Fowler与James Lewis于2014年共同提出来的,微服务架构风格是一种使用一套小服务来开发替代应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,...
  • Go/Golang/Beego微服务基础实战视频教程,完整版17章,提供源码+课件资料下载,本课程从Beego环境搭建开始讲解,涉及Beego知识的方方面面,最后还有“知了课堂内部管理系统”企业级项目实战,让你真正能学以致用!
  • 分享视频教程:Go/Golang/Beego微服务基础实战视频教程,2020年最新录制,完整版400多节视频,附带源码, 本课程从Beego环境搭建开始讲解,涉及Beego知识的方方面面,最后还有企业级项目实战,让你真正能学以致用!
  • 译者按:公司转向重新做一套支付系统,并打算用golang微服务来实现。于是上网上找一找教程,发现中文教程都不是很系统,于是找到了这一篇,自己实践下来,感觉深浅适中,讲解清晰,一步一步可以跟着做下来,之后能...
  • golang搭建项目教程

    2020-01-18 20:22:41
    golang与Java的不同:go的特性体现在通道和高并发,可做中间件,云计算,而java是业务性语言,搭建微服务架构。 项目下的vendor目录 GOROOT/src GOPATH/src 依赖包加到本地: go get github....
  • 为什么使用go-zero 你还在手撕微服务?快试试 go-zero 的微服务自动生成神器,这可能是我见过最简单好用的微服务框架。 还有比它更简单好用的吗?欢迎留言评论和推荐。 几分钟搞定个接口和微服务,还...golang圈子不大
  • 随着微服务的兴起,行业里出现了非常多优秀的微服务网关框架,今天教大家搭建一套国人,用Golang写的微服务网关框架。 这里啰嗦一句,可能到今天还有人不理解什么是微服务,为什么要用微服务。目前网上相对比较模糊...
  • chapin blog 微服务架构 & DevOps K8S Service Mesh 苏槐系列文章 如何快速搭建一个微服务架构 微服务架构中API的开发与治理 如何保障无服务架构下的数据一致性 ...高效sql性能优化极简教程 Mongo高级
  • 【超全golang面试题合集+ golang学习指南+ golang知识图谱+成长路线】一份涵盖大部分golang程序员所需要掌握的核心知识。 后续文章和内容会不断更新到中,欢迎关注。 目录(善用Ctrl + F) 基础入门 新手 数据类型 ...
  • gRpc 文章目录gRpc1. gRpc 概述2. gRpc执行概述3....在微服务风格架构中有效连接多语种服务 将移动设备,浏览器客户端连接到后端服务 生成高效的客户端库 gRpc官方地址 https://grpc.io/ gRpc源码托管

空空如也

空空如也

1 2 3
收藏数 55
精华内容 22
关键字:

golang微服务教程