查看原文。 在本文中,将使用Go编程语言和go-kit作为标准微服务库创建一个简单的微服务。该服务将通过REST端点公开。 微服务 在当今世界,微服务体系结构已得到普及。 我不会特别解释什么是微服务架构,因为互联网上已经讨论了很多。 但是,我将提供两个有关微服务的良好网站。 首先是我最喜欢的martinfowler.com,可以在这里看到他的惊人解释。 另一个来自microservices.io,里面有很多关于模式和示例的好文章。 Go-lang Go是一种开放源代码的编程语言,可轻松构建简单,可靠和高效的软件。该语言由Google设计,旨在解决Google的问题。因此,人们可以希望这种语言能够大规模运行,适用于大型程序和依赖项。 Go-kit Go-kit确实有助于简化构建微服务架构的过程。 这是因为它具有许多功能,例如服务连接性,指标和日志记录。 因此,我要特别感谢Peter Bourgon(@(https://peter.bourgon.org/))和所有提供此出色库的贡献者。 用例 我在本文中的重点是将REST端点公开为服务。服务本身将使用HTTP POST方法公开端点。然后它将以JSON格式返回lorem ipsum消息。 端点URL格式为/lorem/{type}/{min}/{max},其中包含以下说明: type将是lorem类型,即word(单词),sentence(句子)和paragraph(段落) min和max表示生成器生成的最少和最多字母数 一步步操作 在逐步开始之前,此示例需要几个库。那些是: go-kit libraries golorem libraries:用于生成lorem ipsum文本 gorilla mux libraries: 用于http处理程序 第一步:创建Service go get github.com/go-kit/kit go get github.com/drhodes/golorem go get github.com/gorilla/mux 无论使用哪种最酷的工具,都需要在第一时间创建业务逻辑。如用例所述,我们的业务逻辑是根据单词,句子或段落创建lorem ipsum文本。因此,让我们在工作区下创建lorem文件夹。就我而言,我的文件夹是$GOPATH/github.com/ru-rocker/gokit-playground/lorem。然后在该文件夹下创建文件service.go并添加以下代码: // Define service interface type Service interface { Word(min, max int) string // generate a word with at least min letters and at most max letters. Sentence(min, max int) string // generate a sentence with at least min words and at most max words. Paragraph(min, max int) string // generate a paragraph with at least min sentences and at most max sentences. } // Implement service with empty struct type LoremService struct {} 现在我们已经有了接口,但是,没有方法实现的接口将毫无意义。为service.go,添加以下实现: // Implement service functions func (LoremService) Word(min, max int) string { return golorem.Word(min, max) } func (LoremService) Sentence(min, max int) string { return golorem.Sentence(min, max) } func (LoremService) Paragraph(min, max int) string { return golorem.Paragraph(min, max) } 注意,对每个方法实现都使用了golorem函数。 第2步:建模Request和Response 由于此服务是HTTP的一部分,因此下一步是对请求和响应进行建模。如果回头看一下用例,将发现实现需要三个属性,分别是:type、min、max。 对于响应本身,只需要两个字段。 这些字段是包含lorem ipsum文本的消息 另一个是错误字段,当出现错误时,它将给出错误描述 因此,让我们创建另一个文件,并为其命名为endpoints.go,并添加以下代码: //request type LoremRequest struct { RequestType string Min int Max int } //response type LoremResponse struct { Message string `json:"message"` Err error `json:"err,omitempty"` //omitempty 表示,如果值为nil,则不会显示此字段 } 第3步:创建端点 端点是go-kit中的特殊功能,可以将它包装到http.Handler中。为了使我们的service变成endpoint.Endpoint函数,我们将要制作一个函数来处理LoremRequest,在函数内部做一些逻辑,然后返回LoremResponse。对于endpoints.go,添加如下代码: var ( ErrRequestTypeNotFound = errors.New("Request type only valid for word, sentence and paragraph") ) // endpoints wrapper type Endpoints struct { LoremEndpoint endpoint.Endpoint } // creating Lorem Ipsum Endpoint func MakeLoremEndpoint(svc Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(LoremRequest) var ( txt string min, max int ) min = req.Min max = req.Max if strings.EqualFold(req.RequestType, "Word") { txt = svc.Word(min, max) } else if strings.EqualFold(req.RequestType, "Sentence"){ txt = svc.Sentence(min, max) } else if strings.EqualFold(req.RequestType, "Paragraph") { txt = svc.Paragraph(min, max) } else { return nil, ErrRequestTypeNotFound } return LoremResponse{Message: txt}, nil } } 第4步:传输 在处理http请求和响应之前,首先需要创建从struct到json或相反的编码器和解码器。为此,创建一个新文件,命名为transport.go并添加以下代码: // decode url path variables into request func decodeLoremRequest(_ context.Context, r *http.Request) (interface{}, error) { vars := mux.Vars(r) requestType, ok := vars["type"] if !ok { return nil, ErrBadRouting } vmin, ok := vars["min"] if !ok { return nil, ErrBadRouting } vmax, ok := vars["max"] if !ok { return nil, ErrBadRouting } min, _ := strconv.Atoi(vmin) max, _ := strconv.Atoi(vmax) return LoremRequest{ RequestType: requestType, Min: min, Max: max, }, nil } // errorer由可能包含错误的所有具体响应类型实现 // 它使我们能够更改HTTP响应代码,而无需触发端点(传输级)错误 type errorer interface { error() error } // encodeResponse是对客户端的所有响应类型进行编码的常用方法 func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { if e, ok := response.(errorer); ok && e.error() != nil { // 不是Go kit传输错误,而是业务逻辑错误 // 提供这些作为HTTP错误 encodeError(ctx, e.error(), w) return nil } w.Header().Set("Content-Type", "application/json; charset=utf-8") return json.NewEncoder(w).Encode(response) } // encode error func encodeError(_ context.Context, err error, w http.ResponseWriter) { if err == nil { panic("encodeError with nil error") } w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]interface{}{ "error": err.Error(), }) } 一旦声明了编码器和解码器功能,就可以创建http处理程序了。在transport.go中添加以下代码: var ( // 如果缺少预期的路径变量,则返回ErrBadRouting ErrBadRouting = errors.New("inconsistent mapping between route and handler (programmer error)") ) // Make Http Handler func MakeHttpHandler(ctx context.Context, endpoint Endpoints, logger log.Logger) http.Handler { r := mux.NewRouter() options := []httptransport.ServerOption{ httptransport.ServerErrorLogger(logger), httptransport.ServerErrorEncoder(encodeError), } //POST /lorem/{type}/{min}/{max} // 注意:请看这行。这是描述URL路径和HTTP请求方法的方法 r.Methods("POST").Path("/lorem/{type}/{min}/{max}").Handler(httptransport.NewServer( ctx, endpoint.LoremEndpoint, decodeLoremRequest, encodeResponse, options..., )) return r } 第5步:main 到目前为止,已经为服务提供了服务/业务层,端点和传输。设置完成后,就该创建main函数了。main函数基本上是构造端点并使其可用于HTTP传输。 因此,在lorem文件夹下,创建另一个文件夹,名为lorem.d(点d表示守护程序)。可以随意命名,然后创建文件main.go,并添加以下代码: func main() { ctx := context.Background() errChan := make(chan error) var svc lorem.Service svc = lorem.LoremService{} endpoint := lorem.Endpoints{ LoremEndpoint: lorem.MakeLoremEndpoint(svc), } // Logging domain. var logger log.Logger { logger = log.NewLogfmtLogger(os.Stderr) logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC) logger = log.NewContext(logger).With("caller", log.DefaultCaller) } r := lorem.MakeHttpHandler(ctx, endpoint, logger) // HTTP transport go func() { fmt.Println("Starting server at port 8080") handler := r errChan <- http.ListenAndServe(":8080", handler) }() go func() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) errChan <- fmt.Errorf("%s", <-c) }() fmt.Println(<- errChan) } 第6步:运行样例 现在该运行示例了。打开shell输入如下命令: cd $GOPATH go run src/github.com/ru-rocker/gokit-playground/lorem/lorem.d/main.go 使用curl或postman测试端点。 样例代码 每当您对本文感兴趣并愿意了解更多信息时,都可以在我的github上进行检查。
查看原文。 本文将展示如何使用Golang编程语言和Go-kit作为其框架来创建微服务。该服务将通过gRPC协议公开。 概览 在上一篇文章中,我谈到了使用go编程语言创建微服务。另外,我介绍了go-kit作为微服务框架。如您所见,两者在构建服务方面都非常出色。就像以前一样,在本文中,将创建另一个端点作为服务,但是,将使用gRPC协议作为通信接口,而不是通过REST暴露服务。 gRPC 我将简要介绍gRPC,因为grpc.io已经对此进行了很好的解释。gRPC是Google提供的框架,用于支持远程过程调用。通过使用gRPC,客户端可以直接调用运行在其他计算机上的服务的方法。此外,默认情况下,gRPC使用protocol buffers作为序列化结构数据的机制。与XML或JSON相比,此序列化更小且编码和解码更快。 如果想要了解protocol buffers性能与JSON相比有多出色,可以访问此链接。 用例 用例仍然与上一篇文章相同。将创建一个服务来生成“ lorem ipsum”文本。但是这次会有所不同,不会在一项服务中创建三个功能(单词,句子和段落),而是创建一个称为Lorem的功能。然后从该函数中,无论是生成单词,句子还是段落,都将分派请求类型。 一步步操作 假设我们已经从上一篇文章中获得了所需的库。但这还不够,我们需要安装其他库和protocol buffers。 从这里下载protocol buffers的编译器,提取bin文件夹并将其导出到$PATH中。 #PROTOC export PROTOC_HOME=~/opt/protoc-3.2.0-osx-x86_64 export PATH=${PATH}:$PROTOC_HOME/bin/ 在命令行执行如下操作: go get -u github.com/golang/protobuf/{proto,protoc-gen-go} 将GOBIN也要添加到PATH中: # GOPATH export GOPATH=~/workspace-golang/ export GOBIN=$GOPATH/bin/ export PATH=${PATH}:$GOBIN 然后创建新文件夹,为其命名:lorem-grpc。就我而言,我是在$GOPATH/github.com/ru-rocker/gokit-playground/lorem-grpc下完成的。我将其称为WORKDIR。 第1步:Proto 使用protocol buffers第三版的语言(即proto3)来定义数据结构,首先在WORKDIR中创建名叫pb的文件夹,然后在其中创建一个proto文件。在这个proto文件中指定需要使用的语法是proto3,此外,还将定义服务和请求响应对。 在本文中,服务名称是Lorem,以LoremRequest作为输入参数,并返回LoremResponse。 syntax = "proto3";package pb;service Lorem { rpc Lorem(LoremRequest) returns (LoremResponse) ;}message LoremRequest { string requestType = 1; int32 min = 2; int32 max = 3;}message LoremResponse { string message = 1; string err = 2;} 消息内的字段具有以下格式: type name = index type:描述数据类型属性,它可以是string,bool,double,float,int32,int64等。 index:是数据流的整数值,用于指示字段位置 基于proto文件使用protocol buffers编译器和用于protobuf的Go插件生成Go文件。在pb文件夹下,执行如下命令: protoc lorem.proto --go_out=plugins=grpc:. 这将会创建名为lorem.pb.go的文件。 第2步:定义服务 在WORKDIR下创建文件service.go,然后在其中创建Lorem函数。 package lorem_grpc import ( gl "github.com/drhodes/golorem" "strings" "errors" "context" ) var ( ErrRequestTypeNotFound = errors.New("Request type only valid for word, sentence and paragraph") ) // Define service interface type Service interface { // generate a word with at least min letters and at most max letters. Lorem(ctx context.Context, requestType string, min, max int) (string, error) } // Implement service with empty struct type LoremService struct {} // Implement service functions func (LoremService) Lorem(_ context.Context, requestType string, min, max int) (string, error) { var result string var err error if strings.EqualFold(requestType, "Word") { result = gl.Word(min, max) } else if strings.EqualFold(requestType, "Sentence") { result = gl.Sentence(min, max) } else if strings.EqualFold(requestType, "Paragraph") { result = gl.Paragraph(min, max) } else { err = ErrRequestTypeNotFound } return result, err } 第3步:创建端点 需要注意的一件事是,使用Endpoints结构实现Service接口,因为,创建gRPC客户端连接时需要此机制。 package lorem_grpc import ( "github.com/go-kit/kit/endpoint" "context" "errors" ) //request type LoremRequest struct { RequestType string Min int32 Max int32 } //response type LoremResponse struct { Message string `json:"message"` Err string `json:"err,omitempty"` } // endpoints wrapper type Endpoints struct { LoremEndpoint endpoint.Endpoint } // creating Lorem Ipsum Endpoint func MakeLoremEndpoint(svc Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(LoremRequest) var ( min, max int ) min = int(req.Min) max = int(req.Max) txt, err := svc.Lorem(ctx, req.RequestType, min, max) if err != nil { return nil, err } return LoremResponse{Message: txt}, nil } } // Wrapping Endpoints as a Service implementation. // Will be used in gRPC client func (e Endpoints) Lorem(ctx context.Context, requestType string, min, max int) (string, error) { req := LoremRequest{ RequestType: requestType, Min: int32(min), Max: int32(max), } resp, err := e.LoremEndpoint(ctx, req) if err != nil { return "", err } loremResp := resp.(LoremResponse) if loremResp.Err != "" { return "", errors.New(loremResp.Err) } return loremResp.Message, nil } 第4步:对请求和响应建模 需要对请求和响应进行编码/解码,为此,创建model.go并定义编码/解码功能。 package lorem_grpc import ( "context" "github.com/ru-rocker/gokit-playground/lorem-grpc/pb" ) //Encode and Decode Lorem Request func EncodeGRPCLoremRequest(_ context.Context, r interface{}) (interface{}, error) { req := r.(LoremRequest) return &pb.LoremRequest{ RequestType: req.RequestType, Max: req.Max, Min: req.Min, } , nil } func DecodeGRPCLoremRequest(ctx context.Context, r interface{}) (interface{}, error) { req := r.(*pb.LoremRequest) return LoremRequest{ RequestType: req.RequestType, Max: req.Max, Min: req.Min, }, nil } // Encode and Decode Lorem Response func EncodeGRPCLoremResponse(_ context.Context, r interface{}) (interface{}, error) { resp := r.(LoremResponse) return &pb.LoremResponse{ Message: resp.Message, Err: resp.Err, }, nil } func DecodeGRPCLoremResponse(_ context.Context, r interface{}) (interface{}, error) { resp := r.(*pb.LoremResponse) return LoremResponse{ Message: resp.Message, Err: resp.Err, }, nil } 第5步:传输 在此步骤中,需要为grpcServer类型实现LoremServer接口。然后创建函数以返回grpcServer。 package lorem_grpc import ( "golang.org/x/net/context" grpctransport "github.com/go-kit/kit/transport/grpc" "github.com/ru-rocker/gokit-playground/lorem-grpc/pb" ) type grpcServer struct { lorem grpctransport.Handler } // implement LoremServer Interface in lorem.pb.go func (s *grpcServer) Lorem(ctx context.Context, r *pb.LoremRequest) (*pb.LoremResponse, error) { _, resp, err := s.lorem.ServeGRPC(ctx, r) if err != nil { return nil, err } return resp.(*pb.LoremResponse), nil } // create new grpc server func NewGRPCServer(ctx context.Context, endpoint Endpoints) pb.LoremServer { return &grpcServer{ lorem: grpctransport.NewServer( ctx, endpoint.LoremEndpoint, DecodeGRPCLoremRequest, EncodeGRPCLoremResponse, ), } } 第6步:Server 在Go lang中创建gRPC服务器几乎与创建HTTP服务器一样容易。不同的是,我们使用的是tcp协议而不是http。在WORKDIR下,创建文件夹server并创建文件server_grpc_main.go。 package main import ( "net" "flag" "github.com/ru-rocker/gokit-playground/lorem-grpc" context "golang.org/x/net/context" "google.golang.org/grpc" "github.com/ru-rocker/gokit-playground/lorem-grpc/pb" "os" "os/signal" "syscall" "fmt" ) func main() { var gRPCAddr = flag.String("grpc", ":8081","gRPC listen address") flag.Parse() ctx := context.Background() // init lorem service var svc lorem_grpc.Service svc = lorem_grpc.LoremService{} errChan := make(chan error) // creating Endpoints struct endpoints := lorem_grpc.Endpoints{ LoremEndpoint: lorem_grpc.MakeLoremEndpoint(svc), } //execute grpc server go func() { listener, err := net.Listen("tcp", *gRPCAddr) if err != nil { errChan <- err return } handler := lorem_grpc.NewGRPCServer(ctx, endpoints) gRPCServer := grpc.NewServer() pb.RegisterLoremServer(gRPCServer, handler) errChan <- gRPCServer.Serve(listener) }() go func() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) errChan <- fmt.Errorf("%s", <-c) }() fmt.Println(<- errChan) } 第7步:Client 现在该到客户端了,需要创建返回Service的NewClient函数,因为NewClient函数将转换为Endpoint。 package client import ( "github.com/ru-rocker/gokit-playground/lorem-grpc" "github.com/ru-rocker/gokit-playground/lorem-grpc/pb" grpctransport "github.com/go-kit/kit/transport/grpc" "google.golang.org/grpc" ) // Return new lorem_grpc service func NewGRPCClient(conn *grpc.ClientConn) lorem_grpc.Service { var loremEndpoint = grpctransport.NewClient( conn, "Lorem", "Lorem", lorem_grpc.EncodeGRPCLoremRequest, lorem_grpc.DecodeGRPCLoremResponse, pb.LoremResponse{}, ).Endpoint() return lorem_grpc.Endpoints{ LoremEndpoint: loremEndpoint, } } 请注意,我为此功能返回了Endpoint(请参阅有关实现Service接口的步骤3)。然后在cmd目录下创建可执行客户端。 package main import ( "flag" "time" "log" grpcClient "github.com/ru-rocker/gokit-playground/lorem-grpc/client" "google.golang.org/grpc" "golang.org/x/net/context" "github.com/ru-rocker/gokit-playground/lorem-grpc" "fmt" "strconv" ) func main() { var ( grpcAddr = flag.String("addr", ":8081", "gRPC address") ) flag.Parse() ctx := context.Background() conn, err := grpc.Dial(*grpcAddr, grpc.WithInsecure(), grpc.WithTimeout(1*time.Second)) if err != nil { log.Fatalln("gRPC dial:", err) } defer conn.Close() loremService := grpcClient.New(conn) args := flag.Args() var cmd string cmd, args = pop(args) switch cmd { case "lorem": var requestType, minStr, maxStr string requestType, args = pop(args) minStr, args = pop(args) maxStr, args = pop(args) min, _ := strconv.Atoi(minStr) max, _ := strconv.Atoi(maxStr) lorem(ctx, loremService, requestType, min, max) default: log.Fatalln("unknown command", cmd) } } // parse command line argument one by one func pop(s []string) (string, []string) { if len(s) == 0 { return "", s } return s[0], s[1:] } // call lorem service func lorem(ctx context.Context, service lorem_grpc.Service, requestType string, min int, max int) { mesg, err := service.Lorem(ctx, requestType, min, max) if err != nil { log.Fatalln(err.Error()) } fmt.Println(mesg) } 运行 为了测试它的工作方式,首先我们需要运行gRPC服务器。 cd $GOPATH #Running grpc server go run src/github.com/ru-rocker/gokit-playground/lorem-grpc/server/server_grpc_main.go 接下来,执行客户端: cd $GOPATH #Running client go run src/github.com/ru-rocker/gokit-playground/lorem-grpc/client/cmd/main.go lorem sentence 10 20 输入如下: # sentence go run src/github.com/ru-rocker/gokit-playground/lorem-grpc/client/cmd/client_grpc_main.go lorem sentence 10 20 Concurrunt nota re dicam fias, sim aut pecco, die appetitum. # word go run src/github.com/ru-rocker/gokit-playground/lorem-grpc/client/cmd/client_grpc_main.go lorem word 10 20 difficultates # paragraph go run src/github.com/ru-rocker/gokit-playground/lorem-grpc/client/cmd/client_grpc_main.go lorem paragraph 10 20 En igitur aequo tibi ita recedimus an aut eum tenacius quae mortalitatis eram aut rapit montium inaequaliter dulcedo aditum. Rerum tempus mala anima volebant dura quae o modis fama vanescit fit. Nuntii comprehendet ponamus redducet cura sero prout, nonne respondi ipsa angelos comes, da ea saepe didici. Crebro te via hos adsit. Psalmi agam me mea pro da. Audi pati sim da ita praeire nescio faciant. Deserens da contexo e suaveolentiam qualibus subtrahatur excogitanda pusillus grex, e o recorder cor re libidine. Ore siderum ago mei, cura hi deo. Dicens ore curiosarum, filiorum eruuntur munerum displicens ita me repente formaeque agam nosti. Deo fama propterea ab persentiscere nam acceptam sed e a corruptione. Rogo ea nascendo qui, fuit ceterarumque. Drachmam ore operatores exemplo vivunt. Recolo hi fac cor secreta fama, domi, rogo somnis. Sapores fidei maneas saepe corporis re oris quantulum doleam te potu ita lux da facie aut. Benedicendo e tertium nosse agam ne amo, mole invenio dicturus me cognoscere ita aer se memor consulerem ab te rei. Miles ita amaritudo rogo hi flendae quietem invoco quae odor desuper tu. Temptatione dicturus ita mediator ita mundum lux partes miseros percepta seu dicant avaritiam nares contra deseri securus. Ea sobrios tale, rogo sanctis. Ita ne manu uspiam hierusalem, transeam dicite subduntur responsa cor socialiter fit deseri album praeditum. 总结 创建gRPC端点非常简单,与REST端点只有一点点不同。但是,gRPC比REST运行得更快。我的感觉是将来gRPC将取代JSON。 同时,如果您对此示例感兴趣,可以在我的github上检查源代码。