GRPC通信模式

在我们日常的客户端和服务端的通信中,往往是简单的请求-响应风格的通信,这里一个请求会得到一个响应。借助gRPC,我们可以实现不同的进程间通信模式。 接下来就由我来介绍 gRPC的4种通信模式:一元 RPC, 服务端流式 RPC, 客户端流式 RPC, 客户端流式 RPC(双向流式),并会带有一些简单的例子展示各种模式,在这里客户端和服务端都由 go 编写。 一元 RPC 我们先来介绍一元 RPC,这是一种单向通信,客户端调用服务端的远程方法时,客户端发送请求至服务端并获得一个响应。 假设我们现在需要构建一个商城系统,商城系统有一个订单服务,提供了根据订单号查询订单的功能。 下面来实现这个功能,先使用 protocol buffer 来定义服务,然后再使用 gRPC 实现一元 RPC。 syntax = "proto3"; import "google/protobuf/wrappers.proto"; service OrderManagement { rpc getOrder(google.protobuf.StringValue) returns (Order) {} } message Order{ string id = 1; string desc = 2; float price = 3; string destination = 4; repeated string items = 5; } 接下来编写相应的服务端实现代码: func (s *server) GetOrder(ctx context.Context, orderId *wrapper.StringValue) (*pb.Order, error) { ord, exists := orderMap[orderId.Value] if exists { return &ord, status.New(codes.OK, "").Err() } return nil, status.Errorf(codes.NotFound, "Order does not exist. : ", orderId) } 由于只是简单的例子,所以使用了一个简单的 map 来存储订单信息,这里我们可以使用数据库来替代。 ...

十二月 4, 2021 · overstarry

Go常见linter

静态分析是一种编译时的编译器检查,它可以检查代码的语法,语义,结构,等等,但是不能检查代码的运行时的错误。 静态分析一般会集成在项目上线的 Ci 流程中,如果过程中出现了错误,那么项目就不能上线,避免了有问题的代码被部署上线。 常见的 go linter 在 go 语言社区中,已经有了很多的 linter,这些 linter 可以帮助我们检查代码的语法,语义,结构等等,接下来我来介绍一些常见的linter。 go lint go lint 是go 官方推出的最早的 linter, 它可以检查导出函数是否有注释, 变量、函数等名称是否符合官方规范。 随着近年来的发展,各种 linter 已经层出不穷了, go lint 已经被 deprecated and frozen 了,官方推荐使用 Staticcheck 和 go vet 等linter。 go vet go vet 也是官方提供的静态分析工具,其内置了锁拷贝检查、循环变量捕获问题、printf 参数不匹配等工具。 比如下面的例子: package main import "sync" func main() { var wg sync.WaitGroup for _, v := range []int{0,1,2,3} { wg.Add(1) go func() { print(v) wg.Done() }() } wg.Wait() } 执行 go vet 命令会提示相应错误,如下图所示: 正确应该使用如下代码: ...

十一月 28, 2021 · overstarry

WebAssembly In Go

WebAssembly 介绍 WebAssembly是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如C / C ++等语言提供一个编译目标,以便它们可以在Web上运行。它也被设计为可以与JavaScript共存,允许两者一起工作。 Go 1.11 向 WebAssembly 添加了一个实验性端口。Go 1.12 对其某些部分进行了改进,预计 Go 1.13 会进一步改进。 简单入门 1 编写main.go package main import "fmt" func main() { fmt.Println("Hello, WebAssembly!") } 2 使用命令编译 GOOS=js GOARCH=wasm go build -o main.wasm 这将生成一个可以在浏览器中运行的WebAssembly文件。 要在浏览器中执行 main.wasm,我们还需要一个 JavaScript 支持文件和一个 HTML 页面来将所有内容连接在一起。 3 复制 js 支持文件 cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" . 4 创建 HTML 页面 <html> <head> <meta charset="utf-8"/> <script src="wasm_exec.js"></script> <script> const go = new Go(); WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => { go.run(result.instance); }); </script> </head> <body></body> </html> 5 启动 web 服务器 ...

十一月 27, 2021 · overstarry

kratos 自定义handler func 没有请求日志的问题及解决

出现的问题 最近在使用 kratos 开发 api 的时候, 由于通过 proto 生成的 server handler 不符合业务的需求, 需要通过自定义 handlerFunc 来定义接口。 在开发中为了程序的可观测性,我使用了 kratos 提供的 logging 中间件。 在使用的过程中, 我发现自定义的 HandlerFunc 的请求日志没有显示, 而 proto 生成的请求正确显示了。 问题的原因 为了找到出现这种情况的原因, 我在官方的 Github 仓库提了个 issue(#1566), 得到了维护人员的解答。 出现这种情况的原因是 自己定义的 handlerFunc 不走 middleware 中间件, 需要自定义 http filter 才能解决。 解决 经过对 http filter 例子的简单研究, 参考了官方的 logging 中间件, 我自己实现了 logging filter. 代码: package middleware import ( "fmt" "net/http" "time" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/transport" ) func Server(logger log.Logger) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var ( code int32 reason string kind string operation string ) ctx := r.Context() startTime := time.Now() if info, ok := transport.FromServerContext(ctx); ok { kind = info.Kind().String() operation = info.Operation() } next.ServeHTTP(w, r) if se := errors.FromError(nil); se != nil { code = se.Code reason = se.Reason } level, stack := extractError(nil) _ = log.WithContext(ctx, logger).Log(level, "kind", "server", "component", kind, "operation", operation, "args", extractArgs(r), "code", code, "reason", reason, "stack", stack, "latency", time.Since(startTime).Seconds(), ) }) } } // extractArgs returns the string of the req func extractArgs(req interface{}) string { if stringer, ok := req.(fmt.Stringer); ok { return stringer.String() } return fmt.Sprintf("%+v", req) } // extractError returns the string of the error func extractError(err error) (log.Level, string) { if err != nil { return log.LevelError, fmt.Sprintf("%+v", err) } return log.LevelInfo, "" } 顺利的显示了请求的日志: ...

十月 22, 2021 · overstarry

golang随机time.Sleep出现的问题

问题 最近需要使用 rand 包随机 time.Sleep() 的时间, 我的代码是这样的: package main import ( "math/rand" "time" ) func main() { rand.Seed(time.Now().UTC().UnixNano()) n := rand.Intn(10) time.Sleep(n * time.Second) } 会遇到下面的问题: Cannot use 'n * time.Second' (type int) as the type Duration Invalid operation: n * time.Second (mismatched types int and Duration) 解决的方法 使用 time.Duration 转换类型,代码如下: package main import ( "math/rand" "time" ) func main() { rand.Seed(time.Now().UTC().UnixNano()) n := rand.Intn(10) time.Sleep(time.Duration(n) * time.Second) } 问题的原因 time.Sleep 方法接收的类型是 Duration 类型。 func Sleep(d Duration) 当我们要 sleep 5s时, 参数是 5 * time.Second, Second 又是 1000 * Millisecond, Millisecond 是 1000 * Microsecond,Microsecond 是 1000 * Nanosecond,Nanosecond 是 1纳秒,它的类型就是 Duration。最后 5s 就变成 5000000000数值。 ...

十月 15, 2021 · overstarry