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

    2019-01-21 08:08:49
    微服务搭建教程,欢迎大家下载。跟着文档一步一步搭建就可以。
  • 微服务教程-源码

    2021-02-14 00:43:14
    微服务教程
  • spring cloud 微服务 教程 视频 主要包含 服务发现——Netflix Eureka 客服端负载均衡——Netflix Ribbon 断路器——Netflix Hystrix 服务网关——Netflix Zuul 分布式配置——Spring Cloud Config
  • 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来容器化我们的微服务。

    展开全文
  • SpringCloud与微服务教程(Eureka、Ribbon、Feign、Hystrix、Turbine、API Gateway、Zuul、Sidecar),永久链接,大小为23.38G,内含视频教程,示例代码,全是干粮,实在好货。
  • 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创建了一个微服务以及一个客户端。

    展开全文
  • java Spring Cloud微服务教程_all.zip Spring Cloud Eureka Spring Cloud Consumer,Provider Spring Cloud Hystrix Spring Cloud Feign Spring CLoud Stream Spring Cloud Config Spring Cloud Bus
  • Docker 微服务教程

    2018-06-29 14:48:23
    阮一峰的网络日志 » 首页 » 档案上一篇:Docker 入门教程下一篇:Node 定时器详解 分类: 开发者手册Docker 微服务教程作者: 阮一峰日期: 2018年2月13日Docker 是一个容器工具,提供虚拟环境。...

    Docker 微服务教程

    作者: 阮一峰

    日期: 2018年2月13日

    珠峰培训

    Docker 是一个容器工具,提供虚拟环境。很多人认为,它改变了我们对软件的认识。

    站在 Docker 的角度,软件就是容器的组合:业务逻辑容器、数据库容器、储存容器、队列容器......Docker 使得软件可以拆分成若干个标准化容器,然后像搭积木一样组合起来。

    这正是微服务(microservices)的思想:软件把任务外包出去,让各种外部服务完成这些任务,软件本身只是底层服务的调度中心和组装层。

    微服务很适合用 Docker 容器实现,每个容器承载一个服务。一台计算机同时运行多个容器,从而就能很轻松地模拟出复杂的微服务架构。

    上一篇教程介绍了 Docker 的概念和基本用法,本文接着往下介绍,如何在一台计算机上实现多个服务,让它们互相配合,组合出一个应用程序。

    我选择的示例软件是 WordPress。它是一个常用软件,全世界用户据说超过几千万。同时它又非常简单,只要两个容器就够了(业务容器 + 数据库容器),很适合教学。而且,这种"业务 + 数据库"的容器架构,具有通用性,许多应用程序都可以复用。

    为了加深读者理解,本文采用三种方法,演示如何架设 WordPress 网站。

    • 方法 A:自建 WordPress 容器
    • 方法 B:采用官方的 WordPress 容器
    • 方法 C:采用 Docker Compose 工具

    一、预备工作:image 仓库的镜像网址

    本教程需要从仓库下载 image 文件,但是国内访问 Docker 的官方仓库很慢,还经常断线,所以要把仓库网址改成国内的镜像站。这里推荐使用官方镜像 registry.docker-cn.com 。下面是我的 Debian 系统的默认仓库修改方法,其他系统的修改方法参考官方文档

    打开/etc/default/docker文件(需要sudo权限),在文件的底部加上一行。

    
    DOCKER_OPTS="--registry-mirror=https://registry.docker-cn.com"
    

    然后,重启 Docker 服务。

    
    $ sudo service docker restart
    

    现在就会自动从镜像仓库下载 image 文件了。

    二、方法 A:自建 WordPress 容器

    前面说过,本文会用三种方法演示 WordPress 的安装。第一种方法就是自建 WordPress 容器。

    2.1 官方 的 PHP image

    首先,新建一个工作目录,并进入该目录。

    
    $ mkdir docker-demo && cd docker-demo
    

    然后,执行下面的命令。

    
    $ docker container run \
      --rm \
      --name wordpress \
      --volume "$PWD/":/var/www/html \
      php:5.6-apache
    

    上面的命令基于php的 image 文件新建一个容器,并且运行该容器。php的标签是5.6-apache,说明装的是 PHP 5.6,并且自带 Apache 服务器。该命令的三个参数含义如下。

    • --rm:停止运行后,自动删除容器文件。
    • --name wordpress:容器的名字叫做wordpress
    • --volume "$PWD/":/var/www/html:将当前目录($PWD)映射到容器的/var/www/html(Apache 对外访问的默认目录)。因此,当前目录的任何修改,都会反映到容器里面,进而被外部访问到。

    运行上面的命令以后,如果一切正常,命令行会提示容器对外的 IP 地址,请记下这个地址,我们要用它来访问容器。我分配到的 IP 地址是 172.17.0.2。

    打开浏览器,访问 172.17.0.2,你会看到下面的提示。

    
    Forbidden
    You don't have permission to access / on this server.
    

    这是因为容器的/var/www/html目录(也就是本机的docker-demo目录)下面什么也没有,无法提供可以访问的内容。

    请在本机的docker-demo目录下面,添加一个最简单的 PHP 文件index.php

    
    <?php 
    phpinfo();
    ?>
    

    保存以后,浏览器刷新172.17.0.2,应该就会看到熟悉的phpinfo页面了。

    2.2 拷贝 WordPress 安装包

    既然本地的docker-demo目录可以映射到容器里面,那么把 WordPress 安装包拷贝到docker-demo目录下,不就可以通过容器访问到 WordPress 的安装界面了吗?

    首先,在docker-demo目录下,执行下面的命令,抓取并解压 WordPress 安装包。

    
    $ wget https://cn.wordpress.org/wordpress-4.9.4-zh_CN.tar.gz
    $ tar -xvf wordpress-4.9.4-zh_CN.tar.gz
    

    解压以后,WordPress 的安装文件会在docker-demo/wordpress目录下。

    这时浏览器访问http://172.17.0.2/wordpress,就能看到 WordPress 的安装提示了。

    2.3 官方的 MySQL 容器

    WordPress 必须有数据库才能安装,所以必须新建 MySQL 容器。

    打开一个新的命令行窗口,执行下面的命令。

    
    $ docker container run \
      -d \
      --rm \
      --name wordpressdb \
      --env MYSQL_ROOT_PASSWORD=123456 \
      --env MYSQL_DATABASE=wordpress \
      mysql:5.7
    

    上面的命令会基于 MySQL 的 image 文件(5.7版本)新建一个容器。该命令的五个命令行参数的含义如下。

    • -d:容器启动后,在后台运行。
    • --rm:容器终止运行后,自动删除容器文件。
    • --name wordpressdb:容器的名字叫做wordpressdb
    • --env MYSQL_ROOT_PASSWORD=123456:向容器进程传入一个环境变量MYSQL_ROOT_PASSWORD,该变量会被用作 MySQL 的根密码。
    • --env MYSQL_DATABASE=wordpress:向容器进程传入一个环境变量MYSQL_DATABASE,容器里面的 MySQL 会根据该变量创建一个同名数据库(本例是WordPress)。

    运行上面的命令以后,正常情况下,命令行会显示一行字符串,这是容器的 ID,表示已经新建成功了。

    这时,使用下面的命令查看正在运行的容器,你应该看到wordpresswordpressdb两个容器正在运行。

    
    $ docker container ls
    

    其中,wordpressdb是后台运行的,前台看不见它的输出,必须使用下面的命令查看。

    
    $ docker container logs wordpressdb
    

    2.4 定制 PHP 容器

    现在 WordPress 容器和 MySQL 容器都已经有了。接下来,要把 WordPress 容器连接到 MySQL 容器了。但是,PHP 的官方 image 不带有mysql扩展,必须自己新建 image 文件。

    首先,停掉 WordPress 容器。

    
    $ docker container stop wordpress
    

    停掉以后,由于--rm参数的作用,该容器文件会被自动删除。

    然后,在docker-demo目录里面,新建一个Dockerfile文件,写入下面的内容。

    
    FROM php:5.6-apache
    RUN docker-php-ext-install mysqli
    CMD apache2-foreground
    

    上面代码的意思,就是在原来 PHP 的 image 基础上,安装mysqli的扩展。然后,启动 Apache。

    基于这个 Dockerfile 文件,新建一个名为phpwithmysql的 image 文件。

    
    $ docker build -t phpwithmysql .
    

    2.5 Wordpress 容器连接 MySQL

    现在基于 phpwithmysql image,重新新建一个 WordPress 容器。

    
    $ docker container run \
      --rm \
      --name wordpress \
      --volume "$PWD/":/var/www/html \
      --link wordpressdb:mysql \
      phpwithmysql
    

    跟上一次相比,上面的命令多了一个参数--link wordpressdb:mysql,表示 WordPress 容器要连到wordpressdb容器,冒号表示该容器的别名是mysql

    这时还要改一下wordpress目录的权限,让容器可以将配置信息写入这个目录(容器内部写入的/var/www/html目录,会映射到这个目录)。

    
    $ chmod -R 777 wordpress
    

    接着,回到浏览器的http://172.17.0.2/wordpress页面,点击"现在就开始!"按钮,开始安装。

    WordPress 提示要输入数据库参数。输入的参数如下。

    • 数据库名:wordpress
    • 用户名:root
    • 密码:123456
    • 数据库主机:mysql
    • 表前缀:wp_(不变)

    点击"下一步"按钮,如果 Wordpress 连接数据库成功,就会出现下面的页面,这就表示可以安装了。

    至此,自建 WordPress 容器的演示完毕,可以把正在运行的两个容器关闭了(容器文件会自动删除)。

    
    $ docker container stop wordpress wordpressdb
    

    三、方法 B:Wordpress 官方镜像

    上一部分的自建 WordPress 容器,还是挺麻烦的。其实不用这么麻烦,Docker 已经提供了官方 WordPress image,直接用那个就可以了。有了上一部分的基础,下面的操作就很容易理解了。

    3.1 基本用法

    首先,新建并启动 MySQL 容器。

    
    $ docker container run \
      -d \
      --rm \
      --name wordpressdb \
      --env MYSQL_ROOT_PASSWORD=123456 \
      --env MYSQL_DATABASE=wordpress \
      mysql:5.7
    

    然后,基于官方的 WordPress image,新建并启动 WordPress 容器。

    
    $ docker container run \
      -d \
      --rm \
      --name wordpress \
      --env WORDPRESS_DB_PASSWORD=123456 \
      --link wordpressdb:mysql \
      wordpress
    

    上面命令中,各个参数的含义前面都解释过了,其中环境变量WORDPRESS_DB_PASSWORD是 MySQL 容器的根密码。

    上面命令指定wordpress容器在后台运行,导致前台看不见输出,使用下面的命令查出wordpress容器的 IP 地址。

    
    $ docker container inspect wordpress
    

    上面命令运行以后,会输出很多内容,找到IPAddress字段即可。我的机器返回的 IP 地址是172.17.0.3

    浏览器访问172.17.0.3,就会看到 WordPress 的安装提示。

    3.2 WordPress 容器的定制

    到了上一步,官方 WordPress 容器的安装就已经成功了。但是,这种方法有两个很不方便的地方。

    • 每次新建容器,返回的 IP 地址不能保证相同,导致要更换 IP 地址访问 WordPress。
    • WordPress 安装在容器里面,本地无法修改文件。

    解决这两个问题很容易,只要新建容器的时候,加两个命令行参数就可以了。

    先把刚才启动的 WordPress 容器终止(容器文件会自动删除)。

    
    $ docker container stop wordpress
    

    然后,使用下面的命令新建并启动 WordPress 容器。

    
     $ docker container run \
      -d \
      -p 127.0.0.2:8080:80 \
      --rm \
      --name wordpress \
      --env WORDPRESS_DB_PASSWORD=123456 \
      --link wordpressdb:mysql \
      --volume "$PWD/wordpress":/var/www/html \
      wordpress
    

    上面的命令跟前面相比,命令行参数只多出了两个。

    • -p 127.0.0.2:8080:80:将容器的 80 端口映射到127.0.0.28080端口。
    • --volume "$PWD/wordpress":/var/www/html:将容器的/var/www/html目录映射到当前目录的wordpress子目录。

    浏览器访问127.0.0.2:8080:80就能看到 WordPress 的安装提示了。而且,你在wordpress子目录下的每次修改,都会反映到容器里面。

    最后,终止这两个容器(容器文件会自动删除)。

    
    $ docker container stop wordpress wordpressdb
    

    四、方法 C:Docker Compose 工具

    上面的方法 B 已经挺简单了,但是必须自己分别启动两个容器,启动的时候,还要在命令行提供容器之间的连接信息。因此,Docker 提供了一种更简单的方法,来管理多个容器的联动。

    4.1 Docker Compose 简介

    Compose 是 Docker 公司推出的一个工具软件,可以管理多个 Docker 容器组成一个应用。你需要定义一个 YAML格式的配置文件docker-compose.yml,写好多个容器之间的调用关系。然后,只要一个命令,就能同时启动/关闭这些容器。

    
    # 启动所有服务
    $ docker-compose up
    # 关闭所有服务
    $ docker-compose stop
    

    4.2 Docker Compose 的安装

    Mac 和 Windows 在安装 docker 的时候,会一起安装 docker compose。Linux 系统下的安装参考官方文档

    安装完成后,运行下面的命令。

    
    $ docker-compose --version
    

    4.3 WordPress 示例

    docker-demo目录下,新建docker-compose.yml文件,写入下面的内容。

    
    mysql:
        image: mysql:5.7
        environment:
         - MYSQL_ROOT_PASSWORD=123456
         - MYSQL_DATABASE=wordpress
    web:
        image: wordpress
        links:
         - mysql
        environment:
         - WORDPRESS_DB_PASSWORD=123456
        ports:
         - "127.0.0.3:8080:80"
        working_dir: /var/www/html
        volumes:
         - wordpress:/var/www/html
    

    上面代码中,两个顶层标签表示有两个容器mysqlweb。每个容器的具体设置,前面都已经讲解过了,还是挺容易理解的。

    启动两个容器。

    
    $ docker-compose up
    

    浏览器访问 http://127.0.0.3:8080,应该就能看到 WordPress 的安装界面。

    现在关闭两个容器。

    
    $ docker-compose stop
    

    关闭以后,这两个容器文件还是存在的,写在里面的数据不会丢失。下次启动的时候,还可以复用。下面的命令可以把这两个容器文件删除(容器必须已经停止运行)。

    
    $ docker-compose rm
    

    五、参考链接

    (完)

    贝米钱包

    腾讯课堂

    留言(34条)

    另外提供一个vagrant虚拟机安装wordpress的Vagrantfile:https://github.com/laixintao/vagrant-wordpress

    内含Wordpress各个组件的安装脚本。

    哈哈, 我还是第一?

    一直在用docker, 但是在windows下还是有很多问题. 在慢慢研究中.

    win7下可以用 DockerToolbox - 原理是利用VirtualBox

    win10可以直接安装, 但VirtualBox就无法使用了, 两者只能用一个.

    自从用了docker-compose,我现在很多服务都往docker里扔了

    請問 『2.1 官方 的 PHP image』段落的第二個指令

    docker container run \
    --rm \
    --name wordpress \
    --volume "$PWD/":/var/www/html \
    php:5.6-apache

    是否需要提供 -p 選項來揭露 port 呢?

    mac机器上不能运行,172.17.0.3访问不了,利用docker-compose运行之后报错如下:
    Creating dockerdemo_mysql_1 ... done
    Creating dockerdemo_mysql_1 ... 
    Creating dockerdemo_web_1 ... error

    ERROR: for dockerdemo_web_1 Cannot start service web: driver failed programming external connectivity on endpoint dockerdemo_web_1 (e8589fe39aa43060a1c4d2171bc109ffd41f593fb0ea1aede1cbb178c7778797): Error starting userland proxy: listen tcp 127.0.0.3:8080: bind: cannot assign requested address

    ERROR: for web Cannot start service web: driver failed programming external connectivity on endpoint dockerdemo_web_1 (e8589fe39aa43060a1c4d2171bc109ffd41f593fb0ea1aede1cbb178c7778797): Error starting userland proxy: listen tcp 127.0.0.3:8080: bind: cannot assign requested address
    ERROR: Encountered errors while bringing up the project.

    楼上的问题可以试试改变docker-compose.yml 里面的port 设置:
    ports:
    - "127.0.0.3:8080:80" 里面 8080 变成 8888.
    Mac 上一般apache 已经占用了8080端口。可以设置一下禁止apache自动启动,也可以把端口改一下。

    我也遇到楼上的问题, 改了端口也报同样的错误

    我基于官方镜像library/ubuntu:latest创建了容器,安装了mysql-server 5.7可以正常使用,
    但是退出容器后再次进入,就无法启动mysql server,
    一直提示
    /usr/sbin/mysqld: error while loading shared libraries: libaio.so.1: cannot stat shared object: Permission denied
    请问有人知道这是什么原因吗?

    AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
    AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
    [Thu Feb 22 10:27:49.568597 2018] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.10 (Debian) PHP/5.6.33 configured -- resuming normal operations
    [Thu Feb 22 10:27:49.568685 2018] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'

    本机无法ping通172.17.0.2 不知道这个问题有没有遇到的。。。

    AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
    AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
    [Sat Feb 24 07:34:21.648186 2018] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.10 (Debian) PHP/5.6.33 configured -- resuming normal operations
    [Sat Feb 24 07:34:21.648289 2018] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'

    mac上一样的问题, 
    172.17.0.2ping不通.

    @都督府:

    有遇到的,window7 下各种问题,很多命令用不了呀

    大神,建议用 Nginx 和 Docker-Gen https://hub.docker.com/r/jwilder/docker-gen/,启动命令设置 Virtual_Host 和 Virtual_Port

    mac上无法ping容器的问题:

    https://docs.docker.com/docker-for-mac/networking/#known-limitations-use-cases-and-workarounds

    Docker for Mac can’t traffic to containers, and from containers back to the host.

    @win:

    mac上因为系统限制ping不了docker的容器

    所以可以设置-p 8080:80 把端口映射出来

    在这个WordPress的例子中,把数据存储和程序分成两个container有什么好处么?感觉做成一个container更方便些。

    registry.docker-cn.com已经无法访问

    原文:浏览器访问127.0.0.2:8080:80就能看到 WordPress 的安装提示了。
    原文中8080:80应该为8080。

    阮老师,您好!参照您的教程成功跑起了WordPress,但是这个是内部模拟局域网的ip,怎么在别的机器访问呢?

    是我没仔细看 -p 外网端口:容器端口

    教程很棒,从头做了一遍算是入门。

    引用Crazy Boy的发言:

    registry.docker-cn.com已经无法访问

    相同问题,Mac配置完后重启崩溃

    访问 curl http://172.17.0.2/wordpress
    返回这个:


    301 Moved Permanently

    Moved Permanently

    The document has moved here.



    Apache/2.4.10 (Debian) Server at 172.17.0.2 Port 80

    这个算是wordpress的问题吗?

    @win:

    在Mac 上 使用 -p 80:80, 然后访问: http://localhost/
    docker run --rm -p 80:80 --name wordpress --volume "$PWD/":/var/www/html php:5.6-apache

    $ docker container run --rm --name wordpress --volume "$PWD/":/var/www/html php:5.6-apache
    AH00534: apache2: Configuration error: No MPM loaded.


    心塞,有小伙伴遇到这个问题吗

    为社么不把mysql容器中的数据文件影射出来?数据的安全性第一

    - wordpress:/var/www/html
    这里建议用扩展语法,新手容易看不懂,不知道中间没有空格就报错了

    按照文章的步骤我的出现了一个问题,就是所有静态资源的链接是错误的都404了,静态资源请求的host是127.0.0.1,但实际上应该是一个域名

    引用陆崖的发言:

    按照文章的步骤我的出现了一个问题,就是所有静态资源的链接是错误的都404了,静态资源请求的host是127.0.0.1,但实际上应该是一个域名

    已经解决了是nginx反向代理的问题

    引用皮皮李的发言:

    楼上的问题可以试试改变docker-compose.yml 里面的port 设置:
    ports:
    - "127.0.0.3:8080:80" 里面 8080 变成 8888.
    Mac 上一般apache 已经占用了8080端口。可以设置一下禁止apache自动启动,也可以把端口改一下。

    官方文档说了,mac上不用写127.0.0.3,直接写成8080:80就可以,然后用http://localhost:8080/去访问就可以

    @liuderchi:

    我在mac上测试需要,添加-p 8080:80,然后访问的时候用http://localhost:8080/就可以了,不用172.17.0.2这个ip

    第一种第二种都试过了,都不行,第一种显示没权限,第二种 wordpress 启动之后一会子关了,日志是说连接不到mysql

    第一种方法,在win10上运行下面的指令
    docker container run --rm --name wordpress --volume "$PWD/":/var/www/html php:5.6-apache
    报错: Error parsing reference: ":/var/www/html" is not a valid repository/tag: invalid reference format.

    这是什么原因?

    我用java 的springboot 挂在服务器上面用java -jar 启动数据库访问没问题,用noup 命令启动服务器访问不了容器的mysql,用容器内部的ip也访问不了,公网ip3306映射也不能访问,蛋疼n次方

    阮老师,你好,我想问程序运行都会运行相关的文件。现在在一台机器上跑了两个mysql容器。也就是相当于有两个mysql进程。那么两个mysql进程应该各自都有自己的执行文件对吗?这样是不是相当于安装了两次mysql

    我要发表看法

    您的留言 (HTML标签部分可用)

    您的大名:

     «-必填

    电子邮件:

     «-必填,不公开

    个人网址:

     «-我信任你,不会填写广告链接

    记住个人信息?

    展开全文
  • Golang 微服务教程(一)字数统计: 4,073原文链接:ewanvalentine.io,翻译已获作者 Ewan Valentine 授权。本节对 gRPC 的使用浅尝辄止,更多可参考:gRPC 中 Client 与 Server 数据交互的 4 种模式前言系列概览...

    Golang 微服务教程(一)

    字数统计: 4,073

    原文链接: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(货船服务)

    用到的完整技术栈如下:

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

    代码仓库

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

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

    开发环境

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

    $ go envGOARCH="amd64"# macOS 环境GOOS="darwin"# 在第二节使用 Docker 构建 alpine 镜像时需修改为 linuxGOPATH="/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 模式
    • 按照功能做拆分:在更大的项目中可能会将代码封装在处理不同业务的包中,包内部可能会再做拆分

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

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

    a01eab54799823d5732271ce13fb8cd6.png

    微服务有哪些优势?

    降低复杂性

    将整个应用的代码按功能对应拆分为小且独立的微服务代码库,这不禁让人联想到 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 数据就使用 description、weight 等元数据来描述数据本身的意义,在 Browser / Server 架构中用得很多,以方便浏览器解析:

    { "description": "This is a test consignment
    展开全文
  • 19年录制Zookeeper、Dubbo视频教程 微服务教程分布式教程 SpringBoot教程整合 ...
  • 1、Spring Cloud微服务教程 Spring Cloud教程 第一弹 Eureka服务注册中心 Spring Cloud教程 第二弹 客户端负载均衡Ribbon Spring Cloud教程 第三弹 Ribbon工作原理 Spring Cloud教程 第四弹 Hystrix熔断器 ...
  • 微服务教程 微服务最初由Martin Fowler与James Lewis于2014年共同提出来的,微服务架构风格是一种使用一套小服务来开发替代应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,...
  • 38集19年Shiro视频教程Springboot教程整合Shiro 权限教程微服务教程 ...
  • Golang 微服务教程(二) 发表于 2018-05-12 | 阅读次数: | 字数统计: 3,763原文链接:ewanvalentine.io,翻译已获作者 Ewan Valentine 授权。本节未细致介绍 Docker,更多可参考:《第一本Docker书 修订版》前言在上...
  • Spring Cloud 微服务教程(二) 文章目录Spring Cloud 微服务教程(二)1.教程大纲2.Feign客户端-声明式REST调用2.1.分析2.2.Feign的简介2.3.快速入门2.3.1.导入依赖2.3.2.创建一个ItemFeignClient接口2.3.3.改造...
  • Spring Cloud微服务教程(一) 文章目录 Spring Cloud微服务教程(一) 1.教程大纲 2.统一开发环境 3.微服务架构 3.1.单体架构 3.2.单体架构存在的问题 3.3.什么是微服务? 3.4.微服务架构的特征 3.5.微服务架构示例 4....
  • 疯狂Spring Cloud微服务教程 从事十多年的Java EE企业应用开...
  • 微服务教程-使用Spring Cloud
  • docker微服务教程

    2019-01-02 14:09:25
    docker的教程,详细讲述docker的使用,省去Linux下载,安装配置的麻烦,直接使用容器就好,你值得拥有 (●'◡'●)
  • Golang 微服务教程(一)

    千次阅读 2019-01-07 13:59:06
    原文链接:ewanvalentine.io,翻译已获作者 Ewan Valentine ...《Golang 微服务教程》分为 10 篇,总结微服务开发、测试到部署的完整过程。 本节先介绍微服务的基础概念、术语,再创建我们的第一个微服务 consign...
  • 微服务教程--什么是 Nacos 概览 欢迎来到 Nacos 的世界! Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。 ...
  • Spring Boot和ActiveMQ Artemis的生产者消费者微服务教程。 涵盖的主题 Sprint Boot子休息区Api Spring启动批处理 SSL连接 JMS(Java消息服务) Swagger UI用于可视化API 错误处理 基本认证 POJO <-> DTO的映射...
  • 疯狂Spring Cloud微服务教程

    千人学习 2017-11-06 09:11:17
    以Spring Cloud为基础,深入讲解微服务开发的相关框架,包括服务管理框架Eureka、负载均衡框架Ribbon、服务客户端Feign、容错框架Hystrix、消息框架Stream等。
  • SFG的微服务教程(Udemy) 第5节:Spring Boot RestTemplate 54. Apache客户端请求记录 为Apache Http启用调试logging.level.org.apache.http=debug 运行集成测试 启动ArtSfgMsscBreweryApplication 运行测试...
  • 适用人群: 对分布式系统有一定了解的Java开发人员、想要了解并实战微服务架构的人群 ...Spring Cloud是一个微服务架构的工具集,它为我们实现了微服务架构中的各种通用模式,让微服务的开发更加方便、快捷,让微服务...
  • Golang 微服务教程(三) 发表于 2018-05-22 | 阅读次数: | 字数统计: 5,102原文链接:ewanvalentine.io,翻译已获作者 Ewan Valentine 授权。本文完整代码:GitHub在上节中,我们使用 go-micro 重新实现了微服务并...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,137
精华内容 2,054
关键字:

微服务教程