GRPC 错误处理

今天来讲一讲 gRPC 错误处理的方式,以及如何自定义错误处理。当发起 gRPC 调用时,客户端会接受成功状态的响应或带有错误信息状态的错误响应。考虑程序的健壮性,我们需要在编写客户端处理信息时,要处理所有可能的错误。编写服务端代码也要处理错误,并构建合适的错误状态码。 当 gRPC 发生错误时,会返回一个错误码。并附带一条可选的信息,错误状态信息由一个整形状态码和一条字符串消息组成,适用于不同的语言实现。 下图展示了 gRPC 内置的错误码: 缺陷 gRPC 提供的错误模型非常有限,并且与底层的数据格式无关,最常用的数据格式就是 protocol buffers。如果使用了 protocol buffers,google.rpc 提供了更丰富的错误模型,但语言兼容性待测试。 错误处理例子 接下来继续沿用前面的代码,来讲解如何运用错误处理。假如我们需要在订单添加处理中处理非法 ID 请求。如果我们传了一个不合法的 ID 如 -1,需要返回错误给客户端消费者。 func (s *server) AddOrder(ctx context.Context, orderReq *pb.Order) (*wrappers.StringValue, error) { if orderReq.Id == "-1" { log.Printf("Order ID is vaild : %s", orderReq.Id) errorStatus := status.New(codes.InvalidArgument, "Order ID is not valid") ds, err := errorStatus.WithDetails( &epb.BadRequest_FieldViolation{ Field: "ID", Description: fmt.Sprintf("Order ID received is not valid %s : %s", orderReq.Id, orderReq.Description), }, ) if err != nil { return nil, errorStatus.Err() } return nil, ds.Err() } orderMap[orderReq.Id] = *orderReq log.Println("Order : ", orderReq.Id, " -> Added") return &wrapper.StringValue{Value: "Order Added: " + orderReq.Id}, nil } 通过 status 包可以很方便的创建所需的错误码和相应错误详情。使用 Google API 的相应包可以设置更丰富的错误详情。 ...

十二月 18, 2021 · overstarry

GRPC 拦截器

本篇文章我来介绍一下 gRPC 拦截器的使用,拦截器主要用于在服务器端和客户端拦截 RPC. 拦截器可以在 gRPC 中拦截 RPC 的执行,来满足一些特殊的需求,如日志,认证,访问控制等。gRPC 提供了简单的接口,用来在客户端和服务端的 gRPC 协议中添加拦截器。 根据所使用的 gRPC 通信模式的不同,主要分为 2 种拦截器:1)一元拦截器,2)流拦截器。既可以在客户端使用拦截器,也可以在服务端使用拦截器。 接下来,我会依次介绍在服务端和客户端的使用。 服务端拦截器 当客户端调用 gRPC 的远程调用方法时,可以通过服务端拦截器,在执行一些方法前,执行一些通用的操作。如果希望在 Rpc 服务中添加服务端拦截器,只需实现该拦截器,并在创建服务端时注册进来。 下面依次介绍两种服务端拦截器:1)一元拦截器,2)流拦截器。 一元拦截器 如果想在服务端拦截 一元 RPC 调用时,需要在服务端实现相应的函数,此函数的签名为: type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error) 我们在上一篇文章的代码中添加如下代码: func orderUnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { log.Println("======= [Server Interceptor] ", info.FullMethod) log.Printf(" Pre Proc Message : %s", req) m, err := handler(ctx, req) if err != nil { log.Printf("Error : %v", err) } log.Printf(" Post Proc Message : %s", m) return m, err } func main() { initSampleData() lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer(grpc.UnaryInterceptor(orderUnaryServerInterceptor)) pb.RegisterOrderManagementServer(s, &server{}) // Register reflection service on gRPC server. // reflection.Register(s) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } 一元拦截器的实现主要分为三个部分:前置处理、调用 RPC 服务、后置处理。在前置处理阶段可以通过检查参数获得 RPC 的信息,比如 RPC 上下文、请求和服务端信息。 ...

十二月 11, 2021 · overstarry

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

Containerd 配置私有仓库

问题 今天在使用 K8S 部署应用时,遇到容器镜像拉取失败的错误 imagepullbackoff ,通过对应用整体资源清单的分析,发现是使用了使用私有仓库镜像,但是没有配置私有仓库,这样就会导致容器无法拉取镜像,这个问题可以通过配置私有仓库来解决。 由于我使用了 Containerd 容器管理器,所以我需要配置私有仓库,并且配置私有仓库的地址为:https://docker.xx.vip. Containerd 没有 docker 配置私有仓库方便,需要修改 Containerd 配置文件进行配置,配置文件在 /etc/containerd/config.toml 中。 配置私有仓库 Containerd 默认没有此配置文件,需要通过命令导出配置文件 containerd config default> /etc/containerd/config.toml (需要提前创建目录)。 接下来我来讲解如何配置私有镜像仓库。 打开配置文件 找到 [plugins.“io.containerd.grpc.v1.cri”.registry] 修改相应配置,具体配置如下: [plugins."io.containerd.grpc.v1.cri".registry] [plugins."io.containerd.grpc.v1.cri".registry.mirrors] [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.xx.vip"] endpoint = ["https://docker.xx.vip"] [plugins."io.containerd.grpc.v1.cri".registry.auths] [plugins."io.containerd.grpc.v1.cri".registry.configs] [plugins."io.containerd.grpc.v1.cri".registry.configs."docker.xx.vip".auth] username = "xx" password = "xx" [plugins."io.containerd.grpc.v1.cri".registry.headers] 重启 containerd systemctl restart containerd.service 查看 containerd systemctl status containerd.service 回到 K8S 重新拉取镜像,可以看到镜像已经顺利拉取 参考 https://colstuwjx.github.io/2018/02/%E5%8E%9F%E5%88%9B-%E5%B0%8F%E5%B0%9D-containerd%E4%B8%80/ https://github.com/containerd/containerd/issues/5246

十一月 19, 2021 · overstarry

容器出现时间异常问题及解决方法

问题 最近在使用 MinIO SDK 上传资源对象时,出现了一个问题:The difference between the request time and the server's time is too large. 从错误信息可以看出客户端上传资源的时间与 Minio server 的时间相差太大,导致资源上传失败。 解决 根据错误信息得出 server 端的时间出现异常,由于我采用容器部署的方式,因此可以先查看容器的时间是否正确。 进入 Minio 容器 , 执行命令:date, 可以看到如图结果 ( UTC 时间): 而目前的时间是 ( CST 时间) : 可以看出容器的时间与实际的时间相差太大,所以导致资源上传失败。 方法 1 出现了时间异常的问题,可能很多人第一反应就是调整容器的时间,通过 ntpdate 等工具调整时间,这种解决方法方法适用于非容器的 MinIO SERVER 环境调整时间,容器因为一些限制无法使用此方法。 方法 2 在网上查看了许多相关的问题,有了一个针对容器的解决方法,执行: docker run --rm --privileged alpine hwclock -s 可以看到容器时间已经正常了: 我对这条命令不太明白,不明白为什么会影响到其它运行中的容器,可能是与 docker for win 的一些容器实现机制相关。 参考资料 https://stackoverflow.com/questions/4770635/s3-error-the-difference-between-the-request-time-and-the-current-time-is-too-la https://www.cnblogs.com/ryanlamp/p/13387358.html https://github.com/minio/minio-java/issues/701

十一月 12, 2021 · overstarry

Consul 学习

Consul 是一个全功能的服务网格解决方案,解决了操作微服务和云基础设施的网络和安全挑战。Consul 提供了一种软件驱动的路由和分段方法。它还带来了额外的好处,如故障处理、重试和网络可观察性。这些功能中的每一个都可以根据需要单独使用,也可以一起使用,建立一个完整的服务网格。 Consul 是一个分布式系统,旨在运行在一个节点集群上。一个节点可以是一个物理服务器、云实例、虚拟机或容器。连接在一起的 Consul 运行的节点集被称为数据中心。在数据中心内,Consul 可以以服务器或客户端两种模式运行。服务器代理维护 Consul 的一致状态。客户端是一个轻量级的进程,运行在每个运行服务的节点上。一个数据中心将有 3-5 个服务器和许多客户端。 安装 Consul Consul 的安装就不多介绍了,具体可以看看官网的教程 。 运行 Consul 由于我采用了本地安装的方式,我将以开发模式运行 Consul , 在实际生产中不要使用这种方法运行部署。 通过 consul agent -dev 在开发模式下启动 Consul 代理。 可以看到 consul 代理已经顺利启动。 通过 consul members 命令可以查看你的 consul 列表。 输出显示代理、它的 IP 地址、它的健康状态、它在数据中心中的角色以及一些版本信息。您可以通过提供 - 详细标志来发现其他元数据。 通过 members 命令查询的结果可能跟实际不太一样,通过 HTTP API 可以查询准确的结果 curl localhost:8500/v1/catalog/nodes。 使用命令 consul leave 可以停止 consul 代理。 注册服务 接下来介绍在 consul 注册一个服务。 Consul 的主要功能之一是服务发现。Consul 提供了一个 DNS 接口,下游服务可以用它来寻找其上游依赖的 IP 地址。 ...

十一月 6, 2021 · overstarry

Kubernetes 安装 APISIX

今天介绍如何在 k8s 上安装 APISIX 相关组件 (Apache APISIX、Apache APISIX Dashboard、Apache APISIX Ingress Controller)。 Apache APISIX 由于 APISIX helm 组件将相关组件集合在一起了,所以只要装一个就好了。 安装步骤 前提 要创建好相应的 PV . 命令 helm repo add apisix https://charts.apiseven.com helm repo update helm install apisix apisix/apisix --set gateway.type=NodePort --set ingress-controller.enabled=true --set dashboard.enabled=true --set etcd.volumePermissions.enabled=true --namespace ingress-apisix 如果看到如下信息并且执行 kubectl get pods -n ingress-apisix 就表示 apisix 安装成功。 NNAME: apisix LAST DEPLOYED: Wed Oct 27 03:00:23 2021 NAMESPACE: default STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: 1. Get the application URL by running these commands: export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services apisix-gateway) export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}") echo http://$NODE_IP:$NODE_PORT NAME READY STATUS RESTARTS AGE apisix-69459554d4-tc7sd 1/1 Running 0 8m49s apisix-etcd-0 1/1 Running 0 8m49s apisix-etcd-1 1/1 Running 0 8m49s apisix-etcd-2 1/1 Running 0 5m16s apisix-ingress-controller-678d8b5f6d-h6kq8 1/1 Running 0 8m49s 修改 apisix-dashboard service 的 spec.type 为 NodePort 安装过程的问题及解决 etcd pod 启动失败 2021-10-27 06:29:47.259764 C | etcdmain: cannot access data directory: mkdir /bitnami/etcd/data: permission denied ...

十月 27, 2021 · overstarry

设置 Rancher 服务器的本地 Kubernetes 集群

本篇文章介绍如何在 k8s 集群上安装 rancher。 前提 需要安装 kubectl 和 helm。 安装 Rancher Helm Chart 添加 helm chart 仓库 使用 helm repo add rancher-<CHART_REPO> https://releases.rancher.com/server-charts/<CHART_REPO> 添加 helm 仓库。 请将命令中的<CHART_REPO>,替换为 latest,stable 或 alpha。更多信息,请查看选择 Rancher 版本来选择最适合您的仓库。 latest: 建议在尝试新功能时使用。 stable: 建议在生产环境中使用。(推荐) alpha: 未来版本的实验性预览。 创建 Namespace 创建一个名为 cattle-system 的 Namespace。 kubectl create namespace cattle-system 安装 cert-manager # 如果你手动安装了CRD,而不是在Helm安装命令中添加了`--set installCRDs=true`选项,你应该在升级Helm chart之前升级CRD资源。 kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.5.1/cert-manager.crds.yaml # 添加 Jetstack Helm 仓库 helm repo add jetstack https://charts.jetstack.io # 更新本地 Helm chart 仓库缓存 helm repo update # 安装 cert-manager Helm chart helm install cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ --version v1.5.1 安装完 cert-manager 后,您可以通过检查 cert-manager 命名空间中正在运行的 Pod 来验证它是否已正确部署: ...

十月 26, 2021 · overstarry