Tutorial de microservicios de Golang

En esta sección, el uso de gRPC es un poco fácil. Para obtener más información, consulte: 4 modos de interacción de datos entre el Cliente y el Servidor en gRPC

Prólogo

Resumen de la serie

El "Tutorial de microservicios de Golang" se divide en 10 artículos, que resumen el proceso completo de desarrollo, prueba e implementación de microservicios.

Esta sección presenta primero los conceptos básicos y la terminología de los microservicios, y luego crea nuestra primera versión concisa del servicio de consignación de microservicios. En las siguientes secciones 2 ~ 10, crearemos los siguientes microservicios uno tras otro:

  • servicio de consignación (servicio de carga)
  • servicio de inventario (servicio de almacén)
  • servicio de usuario
  • servicio de autenticación
  • servicio de roles
  • servicio de buque (servicio de buque de carga)

La pila tecnológica completa utilizada es la siguiente:

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

Almacén de código

Código de autor: EwanValentine / shippy , código de anotación chino del traductor:  wuYin / shippy

Cada capítulo corresponde a una sucursal del almacén, por ejemplo, el código de la parte1 en este artículo está en la  función / parte1

Entorno de desarrollo

El entorno de desarrollo del autor es macOS, este artículo utiliza la herramienta de creación para compilar de manera eficiente, los usuarios de Windows deben  instalar manualmente

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

Listo

Domina la gramática básica de Golang: se recomienda leer "Go Web Programming" de Xie Da

Instalar  gRPC / protobuf

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

Microservicios

¿Qué proyecto escribiremos?

Queremos construir una plataforma de gestión de carga para el puerto. Este proyecto se desarrolla con una arquitectura de microservicios, que es simple en general y de concepto universal. Sin más preámbulos, comencemos nuestro viaje de microservicios.

¿Qué son los microservicios?

En el desarrollo de software tradicional, el código de toda la aplicación se organiza en una sola base de código, generalmente en la forma del siguiente código dividido:

  • Dividir según las características: como el patrón MVC
  • Dividir según la función: en proyectos más grandes, el código puede encapsularse en paquetes que manejan diferentes negocios, y el paquete puede dividirse nuevamente

No importa cómo se divida, al final, el código de los dos se concentrará en una biblioteca para el desarrollo y la administración, puede consultar: Administración de la biblioteca de código único de Google

Los microservicios son una extensión del segundo método de división descrito anteriormente. El código se divide en varios paquetes según la función, todos los cuales son bibliotecas de código único que se pueden ejecutar de forma independiente. Las diferencias son las siguientes:

image-20180512033801893

¿Cuáles son las ventajas de los microservicios?

Reducir la complejidad

El código de toda la aplicación se divide en bases de código de microservicio pequeñas e independientes de acuerdo con la función, que no puede dejar de recordar la  filosofía de Unix : Do One Thing and Do It Well, en la aplicación de la base de código único tradicional, entre los módulos es Límites estrechamente acoplados y difusos, a medida que el producto continúa iterando, el desarrollo y mantenimiento del código se volverá más complejo y habrá cada vez más posibles errores y vulnerabilidades.

Mejora la escalabilidad

En el desarrollo de proyectos, parte del código se puede usar con frecuencia en múltiples módulos. Estos módulos altamente reutilizables a menudo se extraen y se usan como bases de código público, como los módulos de verificación. Cuando se desea ampliar las funciones (Agregue el inicio de sesión del código de verificación de SMS, etc.), el tamaño de una sola base de código solo aumenta y toda la aplicación debe volver a implementarse. En la arquitectura de microservicios, el módulo de verificación puede aislarse como un solo servicio y puede ejecutarse, probarse e implementarse de forma independiente.

Seguir el concepto de código de división de microservicios puede reducir en gran medida el acoplamiento entre módulos, y la expansión horizontal será mucho más fácil. Es adecuado para el entorno actual de alto rendimiento, alta disponibilidad y desarrollo distribuido de computación en la nube.

Nginx tiene una serie de artículos para explorar muchos conceptos de microservicios,  haga clic aquí para leer

¿Cuáles son los beneficios de usar Golang?

Microservicios es un concepto arquitectónico más que un proyecto de marco específico. Se pueden implementar muchos lenguajes de programación, pero algunos lenguajes tienen ventajas inherentes en el desarrollo de microservicios. Golang es uno de ellos.

Golang en sí mismo es muy ligero y altamente eficiente. Al mismo tiempo, tiene soporte nativo para la programación concurrente, que puede hacer un mejor uso de los procesadores multi-core. La net biblioteca estándar incorporada  también tiene soporte perfecto para el desarrollo de redes. Puede consultar el breve artículo de Xie Da: las ventajas del lenguaje Go

Además, la comunidad de Golang tiene un excelente marco de microservicio de código abierto,  go-mirco , que usaremos en la siguiente sección.

Protobuf y gRPC

En una base de código único para aplicaciones tradicionales, las funciones se pueden llamar directamente entre cada módulo. Sin embargo, en la arquitectura de microservicios, dado que la base de código correspondiente a cada servicio se ejecuta de forma independiente y no se puede llamar directamente, la comunicación entre ellos es un gran problema, y ​​hay 2 soluciones:

API para protocolo JSON o XML

Los microservicios pueden usar el protocolo JSON o XML basado en HTTP para comunicarse: antes de que el servicio A se comunique con el servicio B, A debe codificar los datos que se transferirán al formato JSON / XML y luego pasarlos a B, B en forma de cadena Los datos recibidos deben decodificarse antes de que puedan usarse en el código:

  • Ventajas: los datos son fáciles de leer y de usar, es un protocolo necesario para interactuar con el navegador.
  • Desventajas: en el caso de una gran cantidad de datos, la sobrecarga de codificación y decodificación se hace mayor, y la información de campo adicional conduce a mayores costos de transmisión

API de protocolo RPC

los datos JSON a continuación en el uso  description, weight y otros metadatos para describir el significado de los datos en sí, en la arquitectura navegador / servidor con mucho, a fin de facilitar los análisis sintácticos del navegador:

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

Sin embargo, cuando se comunica entre dos microservicios, si el formato de transmisión de datos se acuerda entre sí, el flujo de datos binarios se puede usar para la comunicación directamente, y los metadatos voluminosos y redundantes ya no son necesarios.

Introducción a gRPC

gRPC  es un marco de comunicación RPC liviano abierto de Google. El protocolo de comunicación se basa en el flujo de datos binarios, lo que hace que gRPC tenga un rendimiento excelente.

gRPC admite el protocolo HTTP 2.0, utiliza tramas binarias para la transmisión de datos y también puede establecer un flujo continuo de datos bidireccional para ambas partes en la comunicación. Ver también: Introducción a Google HTTP / 2

protobuf como protocolo de comunicación

Los dos microservicios se comunican a través de un marco de datos binarios basado en HTTP 2.0, entonces, ¿cómo acordar el formato de los datos binarios? La respuesta es usar el protocolo protobuf integrado en gRPC, y su   sintaxis DSL puede definir claramente la estructura de datos de la comunicación entre servicios. Ver también: gRPC Go: más allá de lo básico

desarrollo de microservicios de servicio de consignación

Después de la explicación conceptual necesaria anterior, comencemos a desarrollar nuestro primer microservicio : consignación-servicio

Estructura del proyecto

Suponiendo que este proyecto se llame  shippy , debe:

  • En  $GOPATH Nueva Shippy directorio del proyecto en el directorio src
  • Crea un nuevo archivo en el directorio del proyecto consignment-service/proto/consignment/consignment.proto

Para la conveniencia de la enseñanza, pondré todos los códigos de microservicio de este proyecto en el directorio shippy. Esta estructura de proyecto se llama "mono-repo", y los lectores también pueden dividir cada microservicio en "multi-repo" Proyecto independiente Más referencia a la  batalla de estilo REPO: MONO VS MULTI

La estructura de su proyecto ahora debería ser la siguiente:

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

Proceso de desarrollo

image-20180512044329199

Definir archivo de protocolo de comunicación 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;// 新托运的货物
}

Referencia de sintaxis:  Protobuf doc

image-20180512010554833

Generar código de protocolo

El compilador de protocolos utiliza el complemento grpc para compilar archivos .proto

Para evitar la ejecución repetida de compilar y ejecutar comandos en la terminal, este proyecto utiliza la herramienta de creación para crear 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

Ejecución  make build, se proto/consignment generará en el  directorio consignment.pb.go

La correspondencia entre consignment.proto y consignment.pb.go

servicio : define la función CreateConsignmentdel servicio de envío del microservicio que se expondrá como la llamada externa : generada por el complemento grpc del compilador protobuf para generar la  interfaz

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

mensaje : define el formato de datos de la comunicación, que procesa el compilador protobuf para generar una  estructura

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"`
    // ...
}

Implemente el servidor

El servidor necesita implementar la  ShippingServiceClient interfaz y crearconsignment-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)
    }
}

El código anterior implementa los métodos necesarios para el microservicio de servicio de consignación y establece un servidor gRPC para escuchar en el puerto 50051. Si ejecuta en este momento  go run main.go, el servidor se iniciará correctamente:

image-20180512051413002

Implementar al cliente

Pondremos la información de los productos a registrar en  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"
}

El cliente leerá este archivo JSON y enviará los productos. Cree un nuevo archivo en el directorio del proyecto: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)
}

Ejecutar  go run main.go nuevamente después de correr  go run cli.go:

grpc-runing

Podemos agregar un RPC para ver todos los envíos y agregar un GetConsignmentsmétodo para que podamos ver todos los existentes 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 {
}

Ahora ejecute make buildpara obtener la última interfaz de microservicio compilada. Si ejecuta en este momento go run main.go, recibirá un mensaje de error similar a este:

image-20180512020710310

Aquellos de ustedes que estén familiarizados con Go deben saber que han olvidado implementar un interfacemétodo requerido. Actualicemos 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)
    }
}

Si lo usa ahora go run main.go, todo debería ser normal:

image-20180512020218724

Finalmente déjenos actualizar consignment-cli/cli.gopara obtener la consignmentinformación:

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)
    }
}

Ejecutar de nuevo en este momento go run cli.go, debería poder ver todo lo creado consignment, varias ejecuciones verán que se consignan varios productos:

veruploading.4e448015.gifTransferencia fallida y re-carga cancelarJietu20180512-053129-HD

Hasta ahora, hemos creado un microservicio y un cliente usando protobuf y grpc.

En el próximo artículo, presentaremos el uso de go-microframeworks y la creación de nuestro segundo microservicio. En el próximo artículo, presentaremos cómo permitir que Docker contenga nuestros microservicios en contenedores.

23 artículos originales publicados · elogiados 2 · visitas 5238

Supongo que te gusta

Origin blog.csdn.net/bianlitongcn/article/details/105131228
Recomendado
Clasificación