对称和反向gRPC连接【译】

原文链接为 https://tilde.town/~hut8/post/grpc-connections/ 使用kimi进行翻译 背景 gRPC是一项出色的现代技术,用于远程过程调用。它允许你在客户端创建一个“存根”对象,该对象的目的是调用服务器上的方法。它是许多情况下REST或GraphQL的绝佳替代品,通常值得学习这项技术。更多信息可以在官方文档中找到。 网络 概念上,我们对客户端和服务器有两种不同的想法。 TCP服务器和客户端 - gRPC在HTTP/2之上运行,HTTP/2在TCP之上运行,所以我将讨论TCP中“服务器”和“客户端”的含义。在TCP连接中,客户端是连接的发起者,服务器是连接的接收者。然而,一旦建立了连接,连接就是对称的;客户端和服务器都可以发送和接收消息,直到一方通过shutdown(2)关闭,或通过close(2)关闭连接。 gRPC服务器和客户端 - gRPC客户端通过存根调用在服务器上运行的方法。这不是对称的。服务器不能在客户端上调用方法。 服务器/客户端类型耦合问题 如果你有一个gRPC客户端,它也是TCP客户端(它调用connect(2))。如果你有一个gRPC服务器,它也是TCP服务器(它调用listen(2)和accept(2))。 因此,如果你想让两台机器可以相互调用gRPC,那么每台机器都是客户端和服务器(在TCP和gRPC两种意义上),现在你有了两个与彼此无关的TCP连接。在有两朵云实例的场景中,这种架构通常并不复杂。但TCP通常很混乱。防火墙、NAT和动态分配的IP地址可能会使客户端到服务器的单向连接变得复杂。 这里有一个这样的情况的例子:假设你的“服务器”是一个在家庭路由器后面的笔记本电脑上运行的程序,它必须接收命令(例如,远程控制)来自“客户端”(例如,云计算实例)。根据gRPC,客户端必须是云计算实例(这很容易被称为“服务器”),因为那是创建远程过程调用的一方。服务器是笔记本电脑,因为那是实际发生过程调用的地方。 当你在客户端(即云计算实例)上创建一个“存根”时,你必须创建一个TCP连接并连接到服务器(即笔记本电脑)。现在你遇到了几个可能的问题: 你不知道笔记本电脑的IP地址,所以你现在需要一个反向服务,让笔记本电脑告诉云它的IP地址。 笔记本电脑可能在NAT后面,所以一旦你找到了IP地址,笔记本电脑将不得不配置端口转发。这在企业环境中很可能是不可能的。 笔记本电脑可能在防火墙后面,这将禁止传入连接。这可能是本地机器上的,也可能是在网关路由器上的。 这些问题中的一些可能是无法解决的,所以我们需要另一种方法。 gRPC独有的解决方案 服务器不能在客户端gRPC上调用方法。也许你可以让客户端在服务器上调用一个方法,其唯一目的是接收描述服务器希望客户端运行的方法的消息。这可以通过流式响应来完成。但这很复杂,需要大量的代码来绕过gRPC的设计。我在其他地方看到过这个建议,但我认为这是一个丑陋的临时解决方案,它制造的问题比它解决的还要多。考虑一下,你将如何在静态类型的方式中实际调用这些“客户端方法”。 基于隧道的解决方案 将TCP客户端/服务器从gRPC客户端/服务器中解耦的一个好方法是实现某种隧道。在上面的例子中,笔记本电脑可以向云计算实例发起一个TCP连接,然后通过实现特定于语言的接口,“拨号”操作让云计算实例(gRPC客户端)连接到笔记本电脑(gRPC服务器)可以简单地使用现有的TCP连接。 SSH是一个不可思议的协议,它的用途比大多数用户知道的还要多。在我们的情况下,它是将我们的TCP连接从gRPC连接中解耦的完美方式。它还有其他好处:尽管gRPC提供认证和加密,但如果更方便,你可以使用SSH提供的。 这些例子是Go语言特有的,但你可以在任何语言中做类似的事情。gRPC服务器不需要监听端口;你可以传入任何实现了Go的net.Listener的类型。所以我们可以做一个net.Listener,它将接受SSH连接,任何时候请求我们的自定义类型的新SSH通道,我们将接受它并返回一个新的net.Conn,这是我们将实现的另一个类型,它只是通过我们的隧道传输数据。 让我们从SSHDataTunnel开始,它是我们的net.Conn。 import ( "net" "time" "golang.org/x/crypto/ssh" ) // SSHDataTunnel实现了net.Conn type SSHDataTunnel struct { Chan ssh.Channel Conn net.Conn } func NewSSHDataTunnel(sshChan ssh.Channel, carrier net.Conn) *SSHDataTunnel { return &SSHDataTunnel{ Chan: sshChan, Conn: carrier, } } func (c *SSHDataTunnel) Read(b []byte) (n int, err error) { return c....

在Go语言中使用Arrow、Flight和Duckdb

从duckdb开始 DuckDB 是一个嵌入式分析型数据库,专为 OLAP(在线分析处理)工作负载设计。本文将基于 go-duckdb 项目的示例,详细介绍 DuckDB 在 Go 语言中的各种使用场景。 基础使用 简单查询 package main import ( "database/sql" _ "github.com/marcboeker/go-duckdb" ) func main() { db, err := sql.Open("duckdb", ":memory:") if err != nil { panic(err) } defer db.Close() // 执行查询 rows, err := db.Query("SELECT 42") if err != nil { panic(err) } defer rows.Close() } 高级特性 Copy COPY 函数可以用于导入导出数据: package main import ( "database/sql" "fmt" _ "github.com/marcboeker/go-duckdb" ) func main() { db, err := sql....

golang检测字节是否为utf-8字节的起始字节

原理 UTF-8 编码使用不同长度的字节序列来表示 Unicode 字符。这些序列的长度从一个字节到四个字节不等,每个字节都有特定的比特模式。您可以通过观察任何字节的最高位(也就是最左边的几位比特),来判断这个字节是不是某个字符的 UTF-8 编码的起始字节或中间字节: 单字节字符(U+0000 到 U+007F)以 0xxxxxxx 为模式,其中 x 可以是 0 或 1。这种单字节的高位为 0,表示它是 ASCII 字符的起始(也是唯一)字节。 UTF-8 编码的起始字节(即多字节序列的第一个字节)模式为 110xxxxx(对于两字节编码)、1110xxxx(对于三字节编码)或 11110xxx(对于四字节编码)。 对于一个 UTF-8 字符的非起始字节(也称为连续字节或中间字节),其模式为 10xxxxxx。 因此,如果你看到一个字节,它的最高两位是 10,那么它是 UTF-8 编码中的一个连续字节。如果最高一位是 0 或者高位模式匹配 110、1110 或 11110,它就是起始字节。 举例来说: 0xxxxxxx - UTF-8 字符的起始字节(单字节 ASCII 字符) 110xxxxx - 两字节编码的起始字节 1110xxxx - 三字节编码的起始字节 11110xxx - 四字节编码的起始字节 10xxxxxx - 中间字节 通过检查字节的最高位,您就可以迅速确定它是 UTF-8 编码序列的起始字节还是中间字节。 实现 go语言实现 package main import ( "fmt" ) // isUTF8StartByte checks whether a byte is a UTF-8 start byte....

go switch的6种用法【译】

文章原文链接为 https://blog.devtrovert.com/p/switch-in-go-6-ways-to-use-it 照片由 Zbyněk Skrčený 在 Unsplash 上拍摄 Go 以其简单而闻名,但我注意到并不是每个人都熟悉 switch 语句在这种语言中的多功能性。 首先,如果您不熟悉 Go 的 switch 语句,与其他语言相比,它可能看起来有点不同。这是一个简单的例子来展示它的样子: func main() { var i int = 1 switch i { case 1: fmt.Println("i is 1") case 2: fmt.Println("i is 2") default: fmt.Println("i is not 1 or 2") } } Go 的 switch 的一个很酷的事情是,一旦找到匹配项,它就会停止,你不需要在每个 case 的末尾添加一个 break 语句。 但不仅仅如此。 Go 中的 switch 语句有两部分:分号之前的部分是初始化器,分号之后的部分是我们要检查的值。 我们可以选择同时使用、使用其中之一或都不使用: switch initializer; value {} switch initializer {} switch value {} switch {} 有趣,对吧?...

使用结构或可变参数选项简化go函数签名【译】

文章内容来自func25的 twitter 在Go中设计函数时,我们可能会遇到需要传递大量参数的情况。 这可能会影响函数的目的,并使维护代码成为一件苦差事,特别是当涉及相同类型的参数时。 为了保持整洁,请考虑两种策略: 选项结构。 可变选项。 选项结构体 将参数捆绑到一个结构中,这不仅增强了可读性,还简化了参数传递。 什么时候使用它? 你的函数有一个很长的参数列表。 您的目标是自记录代码,因为结构字段本质上描述了它们的用途(通过名称)。 您希望轻松设置默认值或灵活修改选项。 使用此模式时, context.Context 应保留为单独的参数,而不应包含在选项结构中。 这是由于上下文在控制请求范围值、截止日期和取消信号方面的独特作用。 但在使用时有一些小技巧: 该结构应该向后兼容,以便在添加新字段时,我们不会破坏之前的任何内容。 我们始终可以在处理结构之前对其进行验证。 考虑隐藏您的选项结构(使其不导出),然后公开 NewXXX() 函数并在那里设置默认值。 可变参数选项 此方法利用 Go 的函数功能,允许您以更简洁的方式传递灵活数量的选项。 它在以下情况下是理想的: 该功能需要高度可配置。 大多数选项是可选的或很少使用。 您更喜欢简洁的调用站点。 设置默认值比使用可选结构更容易,您不需要隐藏它,只需将默认值直接放在 ConnectToDatabase 中即可。

关于七牛qetag算法的一些记录

背景 今天做七牛相关接口开发的时候发现七牛的文件查询列表返回,有个hash字段,但是不知道是怎么进行计算的。后来查询到七牛的官方仓库 qetag.官方对这个算法的描述是这样的: qetag 是一个计算文件在七牛云存储上的 hash 值(也是文件下载时的 etag 值)的实用程序。 七牛的 hash/etag 算法是公开的。算法大体如下: 如果你能够确认文件 <= 4M,那么 hash = UrlsafeBase64([0x16, sha1(FileContent)])。也就是,文件的内容的sha1值(20个字节),前面加一个byte(值为0x16),构成 21 字节的二进制数据,然后对这 21 字节的数据做 urlsafe 的 base64 编码。 如果文件 > 4M,则 hash = UrlsafeBase64([0x96, sha1([sha1(Block1), sha1(Block2), …])]),其中 Block 是把文件内容切分为 4M 为单位的一个个块,也就是 BlockI = FileContent[I*4M:(I+1)*4M]。 为何需要公开 hash/etag 算法?这个和 “消重” 问题有关,详细见: https://developer.qiniu.com/kodo/kb/1365/how-to-avoid-the-users-to-upload-files-with-the-same-key http://segmentfault.com/q/1010000000315810 为何在 sha1 值前面加一个byte的标记位(0x16或0x96)? 0x16 = 22,而 2^22 = 4M。所以前面的 0x16 其实是文件按 4M 分块的意思。 0x96 = 0x80 | 0x16。其中的 0x80 表示这个文件是大文件(有多个分块),hash 值也经过了2重的 sha1 计算。 语言封装 C# 实现 基于官方仓库的csharp代码做了部分修改...

Golang 高级时间工具函数

本文为 https://medium.com/canopas/golang-date-time-utilities-part-2-b1192eb04842 文章的翻译。正文部分进行了部分调整。 背景介绍 在编程世界中,处理日期和时间是一项常见的任务,通常需要精确性和灵活性。 虽然 Go 编程语言的标准库提供了 time 软件包来处理与时间相关的操作,但在某些情况下,开发人员需要额外的实用程序来简化与时间相关的任务。 在本篇博文中,我们将探讨一组实用工具函数,它们是 time 程序包的封装,可为操作提供便利。如果您不了解 time程序包,请考虑在深入学习高级实用程序之前参考一下相关文档。 有关基本功能,请参阅您始终需要的时间实用功能。 🎯 那么,让我们深入探讨一下如何实现。 实现 1.获取月初 一个月的开始是许多日期相关计算的基本参考点。让我们创建一个函数,将日期作为输入,并根据系统时区返回相应月份的第一天: func StartOfMonth(date time.Time) time.Time { return time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, date.Location()) } // function called StartOfMonth(time.Now()) // output 2024-01-01 00:00:00 +0530 IST 此函数将月日设置为 1,其他部分保持不变。 2.获取月末数据 相反,获取月末也同样重要。下面的函数返回给定月份最后一天的最后一秒: func EndOfMonth(date time.Time) time.Time { firstDayOfNextMonth := StartOfMonth(date).AddDate(0, 1, 0) return firstDayOfNextMonth.Add(-time.Second) } // function called EndOfMonth(time.Now()) // output 2024-01-31 23:59:59 +0530 IST 该函数利用之前定义的 StartOfMonth 函数查找下个月的第一天,然后减去一秒,得到当前月份的月底。...

Google Service Weaver中文文档[机翻]

本文为 Service Weaver 文档的机器翻译+细节微调。 Service Weaver是什么 ? Service Weaver 是一个用于编写、部署和管理分布式应用程序的编程框架。您可以在计算机上本地运行、测试和调试 Service Weaver 应用程序,然后使用单个命令将该应用程序部署到云。 $ go run . # Run locally. $ weaver ssh deploy weaver.toml # Run on multiple machines. $ weaver gke deploy weaver.toml # Run on Google Cloud. $ weaver kube deploy weaver.toml # Run on Kubernetes. Service Weaver 应用程序由许多组件组成。组件被表示为常规的 Go 接口,组件之间通过调用这些接口定义的方法进行交互。这使得编写 Service Weaver 应用程序变得容易。您不必编写任何网络或序列化代码;你只要写 Go 就可以了。 Service Weaver 还提供用于日志日志、指标、跟踪、路由、测试等的库。 您可以像运行单个命令一样轻松地部署 Service Weaver 应用程序。在幕后,Service Weaver 将沿着组件边界Profile您的二进制文件,从而允许不同的组件在不同的计算机上运行。 Service Weaver 将为您复制、自动缩放和共同定位这些分布式组件。它还将代表您管理所有网络详细信息,确保不同的组件可以相互通信,并且客户端可以与您的应用程序通信。...

使用go来编写graphql服务

本文为 文章 Go GraphQL Go!!! A beginner’s guide to GraphQL in Go using Ent. 的翻译。大部分使用机翻,部分内容作了相应调整。原文地址为 https://psj.codes/go-graphql-go 您是否想知道计算机和应用程序如何相互通信以获取信息?嗯,他们使用一种称为 API 的东西,它代表应用程序编程接口。 API 充当桥梁,允许不同的软件系统相互通信并交换数据。 在互联网的早期,构建 API 具有挑战性。开发人员必须以一种对每个使用它们的人都有意义的方式进行设计。这就像试图从调酒师那里点一杯饮料,而调酒师的菜单很复杂,有太多的选择。您通常会得到比您需要的更多或不够的信息,例如订购一杯简单的橙汁并收到整个水果篮!这给开发人员带来了挫败感并浪费了时间,他们必须筛选不必要的数据或发出多个请求才能获得他们想要的东西。想象一下,您必须向调酒师要一杯饮料,但您收到的不是简单的订单,而是完整的饮料目录!更糟糕的是,传统 API(称为 REST API)依赖大量端点。这些端点充当访问数据不同部分的特定路径。这就像一个迷宫,里面有无数扇门需要穿过。但随后,改变游戏规则的事情发生了。 Facebook 在 2012 年推出了 GraphQL,这是一种构建 API 的革命性方法,彻底扭转了局面。借助 GraphQL,开发人员终于告别了数据过度获取和数据获取不足的麻烦。快进到 2015 年,Facebook 开源了 GraphQL,并于 2018 年将 GraphQL 捐赠给了 Linux 基金会。 GraphQL 是一种 API 查询语言,或者有人可能会说它是开发 API 的新标准。 在本博客中,我们将探讨 GraphQL 如何应对传统 REST API 面临的挑战。我们还将踏上在 Go 中构建 GraphQL 服务器的实践之旅,为了使我们的开发过程更加令人兴奋和高效,我们将利用 Ent 的强大功能,这是一个专为 Go 设计的令人惊叹的实体库。 GraphQL 救世主 在简介中,我们提到了 RESTful API 的问题。让我们尝试理解它们并研究 GraphQL 如何解决它们。...

golang并发二三事

本文主要是鸟窝《深入理解go并发编程》中的读书速记以及一些并发库的使用例子集合 常用的并发库使用 sourcegraph conc waitgroup 创建一组协程并等待完成: 标准库 func main() { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() // crashes on panic! doSomething() }() } wg.Wait() } conc func main() { var wg conc.WaitGroup for i := 0; i < 10; i++ { wg.Go(doSomething) } wg.Wait() } 下面是一个官网博客的例子: 写一个函数,给定用户的名字,通过网络获取姓氏 func fetchLastName(ctx context.Context, firstName string) (string, error) { req, err := http....