Go wasi

WebAssembly 是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如 C / C ++等语言提供一个编译目标,以便它们可以在 Web 上运行。它也被设计为可以与 JavaScript 共存,允许两者一起工作。 Go 从 1.11 版本就开始支持将 Go 源码编译为 wasm 二进制文件,并在支持 wasm 的浏览器环境中运行。 不过 WebAssembly 绝不仅仅被设计为仅限于在 Web 浏览器中运行,核心的 WebAssembly 语言是独立于其周围环境的,WebAssembly 完全可以通过 API 与外部世界互动。在 Web 上,它自然使用浏览器提供的现有 Web API。然而,在浏览器之外,之前还没有一套标准的 API 可以让 WebAssembly 程序使用。这使得创建真正可移植的非 Web WebAssembly 程序变得困难。WebAssembly System Interface(WASI) 是一个填补这一空白的倡议,它有一套干净的 API,可以由多个引擎在多个平台上实现,并且不依赖于浏览器的功能(尽管它们仍然可以在浏览器中运行)。 Go 1.21 将增加对 WASI 的支持,初期先支持 WASI Preview1 版本,之后会支持 WASI Preview2 版本,直至最终 WASI API 版本发布! go 编译支持 wasi 的程序 怎么样才能编译支持 wasi 的程序呢?我们可以使用 GOOS=wasip1 GOARCH=wasm 将 Go 源码编译为支持 WASI 的 wasm 程序。下面是一个例子: ...

五月 2, 2023 · 1 分钟 · overstarry

gRPC 服务反射协议

本文主要介绍 gRPC 的服务反射协议和相关的应用。 介绍 gRPC 服务反射协议 (server reflection) 是在 gRPC 服务端定义的一个服务,它能提供该服务器端上可公开使用的 gRPC 服务的信息,简单的来说,就是服务反射向客户端提供了服务端注册的服务的信息。因此客户端不需要预编译服务定义就能与服务端交互了。 客户端想要与服务端程序进行通信,必须要有所定义的服务信息,需要编译生产客户端存根,借助 gRPC 服务反射协议,我们就可以无需编译服务定义就能通信。 使用 该如何开启服务反射协议呢?很简单,只需要通过一行代码即可开启:reflection.Register() package main import ( "context" "flag" "fmt" "log" "net" "google.golang.org/grpc" "google.golang.org/grpc/reflection" ecpb "google.golang.org/grpc/examples/features/proto/echo" hwpb "google.golang.org/grpc/examples/helloworld/helloworld" ) var port = flag.Int("port", 50051, "the port to serve on") // hwServer is used to implement helloworld.GreeterServer. type hwServer struct { hwpb.UnimplementedGreeterServer } // SayHello implements helloworld.GreeterServer func (s *hwServer) SayHello(ctx context.Context, in *hwpb.HelloRequest) (*hwpb.HelloReply, error) { return &hwpb.HelloReply{Message: "Hello " + in.Name}, nil } type ecServer struct { ecpb.UnimplementedEchoServer } func (s *ecServer) UnaryEcho(ctx context.Context, req *ecpb.EchoRequest) (*ecpb.EchoResponse, error) { return &ecpb.EchoResponse{Message: req.Message}, nil } func main() { flag.Parse() lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) if err != nil { log.Fatalf("failed to listen: %v", err) } fmt.Printf("server listening at %v\n", lis.Addr()) s := grpc.NewServer() // Register Greeter on the server. hwpb.RegisterGreeterServer(s, &hwServer{}) // Register RouteGuide on the same server. ecpb.RegisterEchoServer(s, &ecServer{}) // Register reflection service on gRPC server. reflection.Register(s) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } 服务端开启服务反射协议后,就可以通过 gRPC CLI 工具来检查服务端了。这里就不多介绍了,接下来我们来看看服务反射协议在 kratos 中的使用。 ...

八月 27, 2022 · 2 分钟 · overstarry

Go 进行浏览器网页截图

前言 在本文中将介绍使用 golang 进行加载某个网站并进行截图。 chromedp 我们将使用 chromedp 通过浏览器驱动来加载网页并截图。具体的步骤如下: 1 启动 chrome 浏览器 2 加载网页 (还可进行其他浏览器操作) 3 截图并保存 需要注意的是项目使用了 Chrome 的驱动,如果没有 Chrome 将不能顺利运行,需要运行 https://hub.docker.com/r/chromedp/headless-shell/ 来进行 或运行其他 版本的 Chrome。 安装 go get -u github.com/chromedp/chromedp 示例 package main import ( "context" "io/ioutil" "log" "github.com/chromedp/chromedp" ) func main() { ctx, cancel := chromedp.NewContext(context.Background(), chromedp.WithDebugf(log.Printf)) defer cancel() url := "https://www.minigame.vip/" filename := "minigame.png" var imageBuf []byte // 捕获某个元素的截图 if err := chromedp.Run(ctx, elementScreenshot(`https://pkg.go.dev/`, `img.Homepage-logo`, &imageBuf)); err != nil { log.Fatal(err) } if err := ioutil.WriteFile("elementScreenshot.png", imageBuf, 0644); err != nil { log.Fatal(err) } if err := chromedp.Run(ctx, ScreenshotTasks(url, &imageBuf)); err != nil { log.Fatal(err) } if err := ioutil.WriteFile(filename, imageBuf, 0644); err != nil { log.Fatal(err) } } func elementScreenshot(urlstr, sel string, res *[]byte) chromedp.Tasks { return chromedp.Tasks{ chromedp.Navigate(urlstr), chromedp.Screenshot(sel, res, chromedp.NodeVisible), } } func ScreenshotTasks(url string, imageBuf *[]byte) chromedp.Tasks { return chromedp.Tasks{ chromedp.Navigate(url), chromedp.FullScreenshot(imageBuf, 90), } } 上面的例子分别是对网页中的单个元素进行截图和对网页全局截图。 ...

八月 20, 2022 · 1 分钟 · overstarry

gopsutil 介绍

简介 psutil 是一个使用 Python 编写的跨平台平台进程和系统利用率监控库,gopsutil 就是 psutil 的 go 语言实现。 使用 安装 go get github.com/shirou/gopsutil/v3 CPU 获取 cpu 基本信息 func getCpuInfo() { cpuInfos, err := cpu.Info() if err != nil { fmt.Printf("get cpu info failed, err:%v", err) } for _, ci := range cpuInfos { fmt.Println(ci) } for { percent, _ := cpu.Percent(time.Second, false) fmt.Printf("cpu percent:%v\n", percent) } } {"cpu":0,"vendorId":"GenuineIntel","family":"205","model":"","stepping":0,"physicalId":"BFEBFBFF000406E3","coreId":"","cores":4,"modelName":"Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz","mhz":2400,"cacheSize":0,"flags":[],"microcode":""} cpu percent:[40.625] cpu percent:[15] cpu percent:[9.615384615384617] cpu percent:[28.125] 获取 cpu 负载 func getCpuLoad() { info, err := load.Avg() if err != nil { panic(err) } fmt.Printf("%v\n", info) } 内存 获取内存 func getMemInfo() { memInfo, _ := mem.VirtualMemory() fmt.Printf("mem info:%v\n", memInfo) } mem info:{"total":8472920064,"available":1921372160,"used":6551547904,"usedPerce nt":77,"free":1921372160,"active":0,"inactive":0,"wired":0,"laundry":0,"buffers" :0,"cached":0,"writeBack":0,"dirty":0,"writeBackTmp":0,"shared":0,"slab":0,"srec laimable":0,"sunreclaim":0,"pageTables":0,"swapCached":0,"commitLimit":0,"commit tedAS":0,"highTotal":0,"highFree":0,"lowTotal":0,"lowFree":0,"swapTotal":0,"swap Free":0,"mapped":0,"vmallocTotal":0,"vmallocUsed":0,"vmallocChunk":0,"hugePagesT otal":0,"hugePagesFree":0,"hugePagesRsvd":0,"hugePagesSurp":0,"hugePageSize":0} 库的其他用法可以查看相应的官方文档。 ...

八月 13, 2022 · 1 分钟 · overstarry

gRPC 健康探针

简介 gRPC 健康探针 grpc-health-probe 是社区提供的一个工具,用来检查 gRPC 服务的健康状态,此工具 是通过 gRPC 健康检查协议公开服务的状态。 使用 我在本地使用 kratos 创建一个使用 9000 端口的 gRPC 的服务。通过 grpc-health-probe 可以检查服务的健康状态。 grpc-health-probe -addr=localhost:9000 status: SERVING 可以看到此服务目前是健康的,不健康的服务将以非零退出代码退出。 grpc_health_probe -addr=localhost:9000 -connect-timeout 250ms -rpc-timeout 100ms failed to connect service at "localhost:9000": context deadline exceeded exit status 2 grpc_health_probe 发送了一个对 /grpc.health.v1.Health/Check 的 RPC 请求。如果已 SERVING 状态作为响应,就会正常成功退出,否则会给出一个非零的退出。 Kubernetes 使用 grpc_health_probe 可用于 Kubernetes 对 Pod 中运行的 gRPC 服务器进行健康检查。建议您使用 Kubernetes exec 探针并为您的 gRPC 服务器 pod 定义活跃度和/或就绪性检查。 您可以将静态编译 grpc_health_probe 的内容捆绑到您的容器映像中。 ...

七月 31, 2022 · 1 分钟 · overstarry

Go 协程闭包的问题

问题 最近在代码中遇到了这么一个问题,现在有一个循环,每一个循环中创建一个协程用来执行函数,我发现函数运行的结果却是大部分时候都是使用最后一个循环变量,不符合实际要求。 大概的代码如下: package main import ( "fmt" "net/http" ) func main() { for i := 0; i < 10; i++ { go func() { fmt.Println(i) }() } http.ListenAndServe(":8080", nil) } 运行: 10 10 10 10 10 10 10 10 10 10 多次运行,可以发现大部分情况都是将 i = 10 代入函数执行。 原因 那这是为什么呢?这个就是函数闭包。协程运行的是一个闭包函数,其中使用了主线程的变量 i。看上去这和第一组几乎一样。但是在每个协程中,从进入匿名函数到调用 Println 将 i 的值复制入栈之间仍需要一小段时间运行,而这段时间内足以主线程完成全部 10 次循环。所以终于到将 i 的值复制入栈调用 Println 时,i 已经成为 10 且不再变化了。 解决 那该怎么解决使代码如我们的需求运行呢? 我们只需将变量 i 复制进栈中即可,改动后的代码: package main import ( "fmt" "net/http" ) func main() { for i := 0; i < 10; i++ { go func(j int) { fmt.Println(j) }(i) } http.ListenAndServe(":8080", nil) } 结果: ...

七月 10, 2022 · 1 分钟 · overstarry

Go 压缩 png 图像大小

起因 最近在处理一个需求,需要将 png 图像按比例调整图像尺寸,要求在保证图像质量的情况下尽量缩小文件大小。在本篇文章主要介绍我将 png 文件大小缩小使用的方法。 方法 这个需求缩小图像的尺寸很好解决,但缩小后的图像大小不尽人意,缩小的图像文件大小没有变化过多,甚至更大。我通过查询,发现了一种方法,就是先将图片转换为 jpeg 格式,再进行压缩后转换为 png 即可。 Jpeg 的图片压缩是很好做的,因为 jpeg 这个协议本身就支持调整图片质量的。在 golang 中,我们只需要使用标准库的 image/jpeg,将图片从二进制数据解码后,降低质量再编码为二进制数据即可实现压缩。而且质量和压缩比例相对而言还不错。 func compressImageResource(data []byte) []byte { imgSrc, _, err := image.Decode(bytes.NewReader(data)) if err != nil { return data } newImg := image.NewRGBA(imgSrc.Bounds()) draw.Draw(newImg, newImg.Bounds(), &image.Uniform{C: color.White}, image.Point{}, draw.Src) draw.Draw(newImg, newImg.Bounds(), imgSrc, imgSrc.Bounds().Min, draw.Over) buf := bytes.Buffer{} err = jpeg.Encode(&buf, newImg, &jpeg.Options{Quality: 40}) if err != nil { return data } if buf.Len() > len(data) { return data } return buf.Bytes() } 小结 本文主要介绍了将 png 图像大小缩小的一种简单方法,还有其他方法等待你们的发掘。

七月 2, 2022 · 1 分钟 · overstarry

Go 处理 zip 解压乱码问题

问题 最近在某个场景中,需要使用 go 官方的 archive/zip 处理 zip 压缩包,在处理过程中,遇到了一个问题:go 解压后的文件存在文件名乱码的情况。 解决 我们知道在 go 中,字符串是以 UTF-8 编码的,所以有可能出现乱码的情况。 我们只要在处理压缩包中的文件时,通过判断 Flags 字段,如果 Flags 为 0 , 则使用本地编码,默认为 GBK。如果为 1 , 则使用 UTF-8 编码。 我们只要在为 0 时对文件名进行处理就好。代码: func Unzip(zipFile string, destDir string) error { zipReader, err := zip.OpenReader(zipFile) if err != nil { return err } defer zipReader.Close() var decodeName string for _, f := range zipReader.File { if f.Flags == 0{ i:= bytes.NewReader([]byte(f.Name)) decoder := transform.NewReader(i, simplifiedchinese.GB18030.NewDecoder()) content,_:= ioutil.ReadAll(decoder) decodeName = string(content) }else{ decodeName = f.Name } fpath := filepath.Join(destDir, decodeName) if f.FileInfo().IsDir() { os.MkdirAll(fpath, os.ModePerm) } else { if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { return err } inFile, err := f.Open() if err != nil { return err } defer inFile.Close() outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { return err } defer outFile.Close() _, err = io.Copy(outFile, inFile) if err != nil { return err } } } return nil } 参考 https://thismj.cn/2019/02/14/qian-xi-zip-ge-shi/ https://chai2010.cn/post/golang/go-zip-utf8/ https://codereview.appspot.com/54360043/ https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT

六月 25, 2022 · 1 分钟 · overstarry

Singleflight 介绍

缓存击穿 对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一 key 缓存,前者则是很多 key。 缓存在某个时间点过期的时候,恰好在这个时间点对这个 Key 有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端 DB 压垮。 我在 go-redis/cache 中发现了库使用了 singleflight , 经过查阅资料,了解了 这个库的主要作用就是将一组相同的请求合并成一个请求,实际上只会去请求一次,然后对所有的请求返回相同的结果。这样会大大降低数据库的压力。 singleflight 使用 函数签名 type Group struct { mu sync.Mutex // protects m m map[string]*call // lazily initialized } // Do 执行函数,对同一个 key 多次调用的时候,在第一次调用没有执行完的时候 // 只会执行一次 fn 其他的调用会阻塞住等待这次调用返回 // v, err 是传入的 fn 的返回值 // shared 表示是否真正执行了 fn 返回的结果,还是返回的共享的结果 func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) // DoChan 和 Do 类似,只是 DoChan 返回一个 channel,也就是同步与异步的区别 func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result // Forget 用于通知 Group 删除某个 key 这样后面继续这个 key 的调用的时候就不会在阻塞等待了 func (g *Group) Forget(key string) 示例 接下来我们来讲解一个简单的例子,我们来看看 singleflight 的使用方式,先来看一个简单的例子: ...

六月 18, 2022 · 2 分钟 · overstarry

Go 并发 Sync.Once 解析

在 go 语言中我们可以使用 sync.Once 对象来实现函数方法只执行一次的功能。 简单代码示例 package main import ( "fmt" "sync" ) func main() { var ( o sync.Once wg sync.WaitGroup ) for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() o.Do(func() { fmt.Printf("hello %d\n", i) }) }(i) } wg.Wait() } 输出: hello 9 不使用 Sync.Once 的结果如下: hello 9 hello 4 hello 0 hello 1 hello 2 hello 3 hello 6 hello 5 hello 7 hello 8 可以看到,在使用 sync.Once 的情况下,只执行一次函数。 ...

六月 3, 2022 · 2 分钟 · overstarry