可观测性主要包括三个方面:追踪(traces)、指标(metrics)和日志(logs)。
OpenTelemetry 的指标功能还是测试状态,没有实现日志的功能,日志可以用其他流行的日志库记录,然后收集到类似ELK的日志系统里面。
追踪数据存储后端是用`jaeger`,指标数据存储后端是用`prometheus`。
前面几个文章实现的是追踪,本文主要讲一下指标。
指标数据直接用`prometheus`的go客户端生成也是一样的,不一定需要用`OpenTelemetry`提供的库。
`OpenTelemetry`的方式是先创建`exporter`和`provider`,之后就可以生成指标了,`main.go`:
```go
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/otel/attribute"
p...
[上一篇文章](https://360us.net/article/87.html)用的是http协议做服务间的调用协议,这篇改成gRPC。
首先安装包`otelgrpc`:
```shell
go get go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
```
创建一个`api`目录,创建`rpc.proto`文件:
```protobuf
syntax = "proto3";
package api;
// advanced目录执行编译: protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative api/rpc.proto
option go_package ="github.com/ilaziness/gopkg/opentelemetry/as/api";
service AsRp...
从上一篇入门:[https://www.360us.net/article/86.html](https://www.360us.net/article/86.html)我们知道用OpenTelemetry实现应用的可观测性需要三个部分:
- `exporter`:负责遥测数据输出,可以输出到控制台,文件,后端存储或者中间的收集节点服务器。
- `instrumentation `:这个部分就是产生追踪数据,也就是创建`span`。
- `TracerProvider`:扮演了中间角色,把生成的遥测数据输出到`exporter`。
目前Go是不支持自动追踪的,一些公共库可以在这里https://opentelemetry.io/ecosystem/registry/?language=go 找到封装好的追踪代码。
比如`otelhttp`是对`net/http`的包装,还有`gin`,`gRPC`的等等,自己的私有库、包、或者函数就需要自己手动添加代码了。
本文的内容是实现在多个服务之间的追踪。
创建三个服务,分别是`main`、`as sevice`...
> 本文定位是快速入门,理解OpenTelemetry在go里面的基本使用
>
> 参考文档:https://opentelemetry.io/docs/instrumentation/go/getting-started
Go 版本需要是1.16以上。
创建一个名字是`fib`的go项目,就一个功能计算斐波那契数列。
创建三个文件:
`fib.go`测试项目的核心,功能是计算斐波那契数列。
```go
package main
import "fmt"
// Fibonacci 计算斐波那契数
func Fibonacci(n uint) (uint64, error) {
if n ...
## 语言变化
### slice转数组
Go1.17在语言层面开始支持将slice转为指向数组的指针。
示例如下:
```go
s := make([]byte, 2, 4)
// 将s这个slice转为指向byte数组的指针s0
// 其中[0]byte里的0表示数组的长度,虽然长度为0,但值不等于nil
s0 := (*[0]byte)(s) // s0 != nil
fmt.Printf("%T")
// 将s[1:]这个slice转为指向byte数组的指针s1
// s1指向的数组的长度为1
s1 := (*[1]byte)(s[1:]) // &s1[0] == &s[1]
// 将s这个slice转为指向byte数组的指针s2
// s2指向的数组的长度为2
s2 := (*[2]byte)(s) // &s2[0] == &s[0]
// 将s这个slice转为指向byte数组的指针s4
// s4指向的数组的长度为4
s4 := (*[4]byte)(s) // panics: len...
基于ZooKeeper和etcd实现服务的注册和发现功能的原理。
## ZooKeeper
### 服务注册
利用临时节点的特性,创建一个服务的临时节点,写入服务信息数据,不断监视节点续期,当服务不可用时会话超时,临时节点会被删除。
### 服务发现
服务发现是利用`watch`的特性,首先获取一遍需要的服务信息,保存在本地,然后给这个服务节点添加`watch`,监视变化和更新。
## etcd
### 服务注册
服务注册是利用了etcd租约的特性。
首先创建一个定时的租约,比如10秒,20秒等。
然后带租约存储服务信息,不断的续期租约,当服务不可用时,租约过期,服务数据就会被删除,就相当于心跳保活了。
### 服务发现
服务发现和zookepper类似,是利用了etcd的`watch`的特性,首先获取一遍需要的服务的信息,再监视变化和更新。
下面是实现代码供参考:
https://github.com/ilaziness/gopkg/tree/main/serviceregdisc...
go的AES加密主要用`crypto/aes`和`crypto/cipher`两个库来实现。
AES加密模式又好多种,这里研究了6种模式:CBC、CFB、OFB、ECB、CTR、GCM模式。
一看到这么多模式,如果那里需要用到一下都不知道哪种好,这里大概总结一下。
如果数据通过非对称签名进行身份验证,则使用CBC,否则使用GCM。
最好的模式是GCM模式,GCM可以保证数据的保密性和完整性,其他模式只能保证保密性,不能保证完整性,具体可以去了解下AEAD。
GCM模式属于AEAD加密。
所以除了GCM模式之外其他模式最好是增加hmac哈希,用来验证数据完整性。
ECB模式不建议使用,因为这种模式不安全,go的内置库也没有去实现这种模式。
go AES 6种加密模式代码:https://github.com/ilaziness/gopkg/tree/main/crypto/aes
iv值的生成可以参考cfb模式的代码,每次加密都是随机生成的。...
Gin模型数据绑定验证器用的是go-playground/validator/v10。默认提示是英文内容,我们需要翻译成中文内容。
创建一个文件`validator.go`:
```go
package utils
import (
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zhtranslations "github.com/go-playground/validator/v10/translations/zh"
"log"
)
var trans ut.Translator
//设置验证器中文翻译
func init() {
if trans != nil {
...
go从1.11版本开始,实验性的加入了`WebAssembly`的支持。
## `hellow world`程序
```go
package main
import "fmt"
func main() {
fmt.Println("Hello, WebAssembly!")
}
```
设置go编译`WebAssembly`环境变量值,`GOOS=js`和`GOARCH=wasm`。
设置方法有两种:
- 修改默认变量值,执行命令:`go env -w GOOS=js GOARCH=wasm`
- 编译的时候指定值:`cd`到项目根目录,执行命令:`GOOS=js GOARCH=wasm go build -o main.wasm`
这时将生成一个`main.wasm`文件,`.wasm`后缀的文件可以通过`http`设置合适的`Content-Type`http头来使用。
需要注意的一点是,只能编译`main`包,否则是不能在`WebAssembly`里面使用的。
**在浏览器里面运行`main.wasm`**
...
服务器端:package main
import (
"bufio"
"bytes"
"fmt"
"net"
"os"
"strconv"
"time"
)
var User map[string]*net.TCPConn
func main() {
var tcpAddr *net.TCPAddr
var tcpListen *net.TCPListener
var tcpConn *net.TCPConn
var err error
User = make(map[string]*net.TCPConn)
//监听端口
tcpAddr, err = net.ResolveTCPAddr("tcp4", ":5000")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
//监听
tcpListen, err = net.ListenTCP("tcp4", t...