热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Go语言中的微服务

摘要我最近在墨尔本Golang聚会上就如何开发微服务和框架做了一次演讲。在本文中,我将与您分享我的想法(此外,它对我来说是一个很好的复习)。在这里,我要介绍以下框架:GoMicro

摘要

我最近在墨尔本 Golang 聚会上就如何开发微服务和框架做了一次演讲。在本文中,我将与您分享我的想法(此外,它对我来说是一个很好的复习)。

在这里,我要介绍以下框架:

  • Go Micro
  • Go Kit
  • Gizmo
  • Kite

框架简介

Go Micro

这是我认为最受欢迎的框架之一。有很多博客文章和简单的例子可供使用参考。您可以从 microhq 在 Medium 或 @MicroHQ 获得 Go Micro 的最新更新。

那么,什么是 Go Micro ?

它是一个可拔插的 RPC 框架,用于在 Go 中编写微服务。开箱即用,您将看到:

  • 服务发现 - 自动向服务发现系统注册的应用程序。
  • 负载均衡 - 客户端负载均衡,用于平衡服务实例之间请求的负载。
  • 同步通信 - 提供请求/响应传输层。
  • 异步通信 - 内置发布/订阅功能。
  • 消息编码 - 基于消息的 Content-Type 请求头的编码/解码。
  • RPC 客户端/服务器打包 - 利用上述特性并公开接口来构建微服务。

    Go Micro 架构可以描述为三层堆栈。

Go语言中的微服务

顶层包括 Server-Client 模型和服务抽象。该服务器是用于编写服务的基础。而客户端提供了一个接口,用于向服务端发起请求。

底层包含以下类型的插件:

  • Broker - 提供一个消息代理接口,用于异步发布/订阅通信
  • Codec - 用于编码/解码消息。支持的格式包括 json,bson,protobuf,msgpack 等。
  • Registry - 提供服务发现机制(默认为 Consul )。
  • Selector - 基于注册表构建的负载均衡抽象。 它允许使用诸如 random,roundrobin,leastconn 等算法“选择”服务。
  • Transport - 服务之间同步请求/响应通信的接口。

    Go Micro 还提供 Sidecar 等功能。这允许您使用Go以外的语言编写的服务。 Sidecar 提供服务注册,gRPC 编码/解码和HTTP处理程序。它有多种语言版本。

Go Kit

Go Kit 是一个用于在Go中构建微服务的编程工具包。与 Go Micro 不同,它是一个旨在导入二进制包的库。

Go Kit 遵循简单的规则,例如:

  • 没有全局状态
  • 声明性构造
  • 显式依赖
  • 接口作为契约
  • 领域驱动设计

在 Go Kit 中,您可以找到以下包:

  • 身份验证 - basic和JWT。
  • 传输 - HTTP,Nats,gRPC 等。
  • 日志记录 - 服务中结构化日志记录的通用接口。
  • 软件度量 - CloudWatch,Statsd,Graphite等。
  • 追踪 - Zipkin 和 Opentracing。
  • 服务发现 - Consul,Etcd,Eureka等。
  • 熔断器 - Hystrix 的 Go 语言实现。

您可以在Peter Bourgon的文章和演示幻灯片中找到 Go Kit 的最佳描述之一:

  • Go kit: Go in the modern enterprise
  • Go + microservices

此外,在“Go + microservices”幻灯片中,您将找到使用 Go Kit 构建的服务架构的示例。 有关快速参考,请参阅服务架构图。

Go语言中的微服务

Gizmo

Gizmo 是纽约时报的微服务工具包。它提供了将服务器和 pubsub 守护进程组合在一起的软件包。它公开了以下包:

  • server - 提供两种服务器实现:SimpleServer(通过 HTTP ),RPCServer(通过 gRPC )。
  • server/kit - 基于 Go Kit 的实验包。
  • config - 包含功能:解析 JSON 文件,Consul 键值对中的 JSON blob ,或者环境变量。
  • pubsub - 提供通用接口,用于从队列中发布和使用数据。
  • pubsub/pubsubtest - 包含发布者和订阅者接口的测试实现。
  • web - 公开用于从请求查询和有效负载中解析类型的函数。

Pubsub包提供了使用以下队列的接口:

  • pubsub/aws - 适用于 Amazon SNS/SQS。
  • pubsub/gcp - 适用于 Google Pubsub。
  • pubsub/kafka - 适用于 Kafka主题。
  • pubsub/http - 用于通过 HTTP 发布。

因此,在我看来,Gizmo 介于 Go Micro 和 Go Kit 之间。它不像 Go Micro 那样完全的“黑盒”。与此同时,它并不像 Go Kit 那么粗糙。它提供更高级别的构建组件,例如config和pubsub包。

Kite

Kite 是一个在 Go 中开发微服务的框架。它公开了 RPC 客户端和服务端的包。 创建的服务会自动注册到服务发现系统 Kontrol 。Kontrol 是用 Kite 写的,它本身就是 Kite 服务。 这意味着 Kite 微服务在自己的环境中运行良好。如果您需要将 Kite 微服务连接到另一个服务发现系统, 则需要进行自定义。这是我从名单中删除 Kite 的主要原因,并决定不讨论这个框架。

比较框架

我将使用四个类别比较框架:

  • 客观比较 - GitHub 统计
  • 文档和示例
  • 用户和社区
  • 代码质量。

GitHub统计

Go语言中的微服务

文档和示例

简单来说,没有一个框架提供可靠的文档。通常,唯一的正式文档是 repo 首页上的 Readme 文件。

对于 Go Micro,可以在 micro.mu,microhq 和社交媒体 @MicroHQ 上获得大量信息和公告。

如果是 Go Kit,您可以在 Peter Bourgon 的博客中找到最好的文档。我发现的一个最好的例子是在 ru-rocker 博客中。

使用 Gizmo,源代码提供了最好的文档和示例。

总而言之,如果你来自 NodeJS 世界,并希望看到类似 ExpressJS 的教程,你会感到失望。 另一方面,这是创建自己的教程的绝佳机会。

用户和社区

Go Kit 是最受欢迎的微服务框架,基于 GitHub 统计数据 - 在本出版物发布时超过10k星。它拥有大量的贡献者(122)和超过1000个分叉。 最后,Go Kit 由 DigitalOcean 提供支持。

Go Micro 第二,拥有超过 3600 颗 stars ,27 个贡献者和 385 个 forks 。Six Micro 的最大赞助商之一是 Sixt。 Gizmo 第三,超过 2200 颗 star, 31 个贡献者和 137 个 forks 。由纽约时报支持和创建。

代码质量

  • Go Kit 在代码质量类别中排名第一。它拥有近 80% 的代码覆盖率和出色的 Go 报告评级。
  • Gizmo 也有很好的 Go 报告评级。但它的代码覆盖率仅为 46%。
  • Go Micro 不提供覆盖率信息,但它确实具有很好的 Go 报告评级。

微服务代码实践

好吧,已有足够的理论。下边,为了更好地理解框架,我创建了三个简单的微服务。

Go语言中的微服务

这些是实现一个业务功能的服务——"Greeting"。 当用户将 "name" 参数传递给服务器时,该服务会发送 Greeting 响应。此外,所有服务均符合以下要求:

  • 服务应自行注册服务发现系统。
  • 服务应具有健康检查接口。
  • 服务应至少支持 HTTP 和 gRPC 传输。

对于那些喜欢阅读源代码的人。 您可以在此处阅读 repo 中的源代码.

Go Micro greeter

使用 Go Micro 创建服务需要做的第一件事是定义 protobuf 描述。 方便后期,所有三项服务都采用了相同的 protobuf 定义。我创建了以下服务描述:

syntax = "proto3";

package pb;

service Greeter {
  rpc Greeting(GreetingRequest) returns (GreetingResponse) {}
}

message GreetingRequest {
  string name = 1;
}

message GreetingResponse {
  string greeting = 2;
}

接口包含一种方法—— "Greeting"。 请求中有一个参数—— 'name',响应中有一个参数 - 'greeting'。

然后我使用修改后的 protoc工具 通过 protobuf 文件生成服务接口。 该生成器由 Go Micro fork 并进行了修改,以支持该框架的一些功能。 我在 “greeting” 服务中将这些连接在一起。此时,该服务正在启动并注册服务发现系统。 它只支持 gRPC 传输协议:

package main

import (
    "log"

    pb "github.com/antklim/go-microservices/go-micro-greeter/pb"
    "github.com/micro/go-micro"
    "golang.org/x/net/context"
)

// Greeter 实现了 greeter 服务.
type Greeter struct{}

// Greeting 方法实现.
func (g *Greeter) Greeting(ctx context.Context, in *pb.GreetingRequest, out *pb.GreetingResponse) error {
    out.Greeting = "GO-MICRO Hello " + in.Name
    return nil
}

func main() {
    service := micro.NewService(
        micro.Name("go-micro-srv-greeter"),
        micro.Version("latest"),
    )

    service.Init()

    pb.RegisterGreeterHandler(service.Server(), new(Greeter))

    if err := service.Run(); err != nil {
        log.Fatal(err)
    }
}

为了支持HTTP传输,我不得不添加其他模块。它将HTTP请求映射到 protobuf 定义的请求。并称为 gRPC 服务。 然后,它将服务响应映射到 HTTP 响应并将其回复给用户。

package main

import (
    "context"
    "encoding/json"
    "log"
    "net/http"

    proto "github.com/antklim/go-microservices/go-micro-greeter/pb"
    "github.com/micro/go-micro/client"
    web "github.com/micro/go-web"
)

func main() {
    service := web.NewService(
        web.Name("go-micro-web-greeter"),
    )

    service.HandleFunc("/greeting", func(w http.ResponseWriter, r *http.Request) {
        if r.Method == "GET" {
            var name string
            vars := r.URL.Query()
            names, exists := vars["name"]
            if !exists || len(names) != 1 {
                name = ""
            } else {
                name = names[0]
            }

            cl := proto.NewGreeterClient("go-micro-srv-greeter", client.DefaultClient)
            rsp, err := cl.Greeting(context.Background(), &proto.GreetingRequest{Name: name})
            if err != nil {
                http.Error(w, err.Error(), 500)
                return
            }

            js, err := json.Marshal(rsp)
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }

            w.Header().Set("Content-Type", "application/json")
            w.Write(js)
            return
        }
    })

    if err := service.Init(); err != nil {
        log.Fatal(err)
    }

    if err := service.Run(); err != nil {
        log.Fatal(err)
    }
}

非常简单明了。 Go Micro 在幕后处理了许多事情——例如在服务发现系统中注册。 另一方面,创建纯 HTTP 服务很困难。

Go Kit greeter

完成 Go Micro 后,我转到了 Go Kit 服务实现。 我花了很多时间阅读 Go Kit 存储库中提供的代码示例。 理解端点的概念花了我很多时间。下一个耗时的难题是服务发现注册商的代码。直到在找到一个 不错的例子 后我才实现它。

最后,我创建了四个包:

  • 服务逻辑实现。
  • 与传输无关的服务端点。
  • 传输特定端点 (gRPC,HTTP)
  • 服务发现注册商。
package greeterservice

// Service 定义 greetings 服务接口.
type Service interface {
    Health() bool
    Greeting(name string) string
}

// GreeterService 实现 Service 接口.
type GreeterService struct{}

// Health 实现 Service 接口 Health 方法.
func (GreeterService) Health() bool {
    return true
}

// Greeting 实现 Service 接口 Greeting 方法.
func (GreeterService) Greeting(name string) (greeting string) {
    greeting = "GO-KIT Hello " + name
    return
}

如您所见,代码没有任何依赖关系。它只是实现逻辑。下一个代码段展示了端点定义:

package greeterendpoint

import (
    "context"

    "github.com/go-kit/kit/log"

    "github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterservice"
    "github.com/go-kit/kit/endpoint"
)

// Endpoints 包含了所有组成 greeter 服务的端点。
// 它被用作一个辅助结构,将所有端点收集到一个参数中。
type Endpoints struct {
    HealthEndpoint   endpoint.Endpoint // used by Consul for the healthcheck
    GreetingEndpoint endpoint.Endpoint
}

// MakeServerEndpoints 返回服务端点, 绑定在提供的中间件上。
func MakeServerEndpoints(s greeterservice.Service, logger log.Logger) Endpoints {
    var healthEndpoint endpoint.Endpoint
    {
        healthEndpoint = MakeHealthEndpoint(s)
        healthEndpoint = LoggingMiddleware(log.With(logger, "method", "Health"))(healthEndpoint)
    }

    var greetingEndpoint endpoint.Endpoint
    {
        greetingEndpoint = MakeGreetingEndpoint(s)
        greetingEndpoint = LoggingMiddleware(log.With(logger, "method", "Greeting"))(greetingEndpoint)
    }

    return Endpoints{
        HealthEndpoint:   healthEndpoint,
        GreetingEndpoint: greetingEndpoint,
    }
}

// MakeHealthEndpoint 构造封装服务的 Health 端点。
func MakeHealthEndpoint(s greeterservice.Service) endpoint.Endpoint {
    return func(ctx context.Context, request interface{}) (response interface{}, err error) {
        healthy := s.Health()
        return HealthResponse{Healthy: healthy}, nil
    }
}

// MakeGreetingEndpoint 构造封装服务的 Greeter 端点。
func MakeGreetingEndpoint(s greeterservice.Service) endpoint.Endpoint {
    return func(ctx context.Context, request interface{}) (response interface{}, err error) {
        req := request.(GreetingRequest)
        greeting := s.Greeting(req.Name)
        return GreetingResponse{Greeting: greeting}, nil
    }
}

// Failer 是实现响应类型的接口。
// 响应可以被检验,是否是 Failer 接口,如果是,那么就是失败的响应,
// 而且,如果是,则根据错误使用单独的写路径对它们进行编码
type Failer interface {
    Failed() error
}

// HealthRequest 包含了 Health 方法的所有请求参数.
type HealthRequest struct{}

// HealthResponse 包含了 Health 方法的响应值。
type HealthResponse struct {
    Healthy bool  `json:"healthy,omitempty"`
    Err     error `json:"err,omitempty"`
}

// Failed 实现 Failer 接口。
func (r HealthResponse) Failed() error { return r.Err }

// GreetingRequest 包含了 Greeting 方法的所有请求参数.
type GreetingRequest struct {
    Name string `json:"name,omitempty"`
}

// GreetingResponse 包含了 Greeting 方法的响应值
type GreetingResponse struct {
    Greeting string `json:"greeting,omitempty"`
    Err      error  `json:"err,omitempty"`
}

// Failed 实现 Failer 接口。
func (r GreetingResponse) Failed() error { return r.Err }

在定义了服务和端点之后,我开始通过不同的传输协议公开端点。我从 HTTP 传输开始:

package greetertransport

import (
    "context"
    "encoding/json"
    "errors"
    "net/http"

    "github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint"
    "github.com/go-kit/kit/log"
    httptransport "github.com/go-kit/kit/transport/http"
    "github.com/gorilla/mux"
)

var (
    // ErrBadRouting 无效路径错误.
    ErrBadRouting = errors.New("inconsistent mapping between route and handler")
)

// NewHTTPHandler 返回一个 HTTP 处理程序(handler),该处理程序使一组端点在预定义的路径上可用。
func NewHTTPHandler(endpoints greeterendpoint.Endpoints, logger log.Logger) http.Handler {
    m := mux.NewRouter()
    options := []httptransport.ServerOption{
        httptransport.ServerErrorEncoder(encodeError),
        httptransport.ServerErrorLogger(logger),
    }

    // GET /health         查找服务健康信息
    // GET /greeting?name  查找 greeting

    m.Methods("GET").Path("/health").Handler(httptransport.NewServer(
        endpoints.HealthEndpoint,
        DecodeHTTPHealthRequest,
        EncodeHTTPGenericResponse,
        options...,
    ))
    m.Methods("GET").Path("/greeting").Handler(httptransport.NewServer(
        endpoints.GreetingEndpoint,
        DecodeHTTPGreetingRequest,
        EncodeHTTPGenericResponse,
        options...,
    ))
    return m
}

// DecodeHTTPHealthRequest 方法.
func DecodeHTTPHealthRequest(_ context.Context, _ *http.Request) (interface{}, error) {
    return greeterendpoint.HealthRequest{}, nil
}

// DecodeHTTPGreetingRequest 方法.
func DecodeHTTPGreetingRequest(_ context.Context, r *http.Request) (interface{}, error) {
    vars := r.URL.Query()
    names, exists := vars["name"]
    if !exists || len(names) != 1 {
        return nil, ErrBadRouting
    }
    req := greeterendpoint.GreetingRequest{Name: names[0]}
    return req, nil
}

func encodeError(_ context.Context, err error, w http.ResponseWriter) {
    w.WriteHeader(err2code(err))
    json.NewEncoder(w).Encode(errorWrapper{Error: err.Error()})
}

func err2code(err error) int {
    switch err {
    default:
        return http.StatusInternalServerError
    }
}

type errorWrapper struct {
    Error string `json:"error"`
}

// EncodeHTTPGenericResponse is a transport/http.
// EncodeResponseFunc 返回 json 响应。

推荐阅读
  • [翻译]微服务设计模式5. 服务发现服务端服务发现
    服务之间需要互相调用,在单体架构中,服务之间的互相调用直接通过编程语言层面的方法调用就搞定了。在传统的分布式应用的部署中,服务地 ... [详细]
  • php网站设计实验报告,php网站开发实训报告
    本文目录一览:1、php动态网站设计的关键技术有哪些软件,及搭建步骤需要哪些页面,分别完成 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 开发笔记:计网局域网:NAT 是如何工作的?
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了计网-局域网:NAT是如何工作的?相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 抖音服务器带宽有多大,才能供上亿人同时刷?
    最近看到一个有意思的提问:抖音服务器带宽有多大,为什么能够供那么多人同时刷?今天来给大家科普一下。 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 众筹商城与传统商城的区别及php众筹网站的程序源码
    本文介绍了众筹商城与传统商城的区别,包括所售产品和玩法不同以及运营方式不同。同时还提到了php众筹网站的程序源码和方维众筹的安装和环境问题。 ... [详细]
  • 项目需要将音视频文件上传服务器,考虑并发要求高,通过七牛来实现。直接上代码usingQiniu.IO;usingQiniu.IO.Resumable;usingQiniu.RPC; ... [详细]
author-avatar
王丹__-划_402
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有