在 Go 语言中,errgroup 是一个非常有用的包,用于在并发操作中集中处理错误。它属于 golang.org/x/sync/errgroup 包,专门设计用来协调多个 Goroutine,并处理可能发生的错误。 为什么使用 errgroup? 当你在 Go 中启动多个 Goroutine 并希望等待它们全部完成时,通常你会使用 sync.WaitGroup 来做同步工作。然而,在多 Goroutine 运行的过程中,你也可能希望捕获其中一个或多个 Goroutine 中发生的错误。errgroup 的作用就是帮助你简化错误处理,它允许你在等待所有 Goroutine 完成时集中管理和返回错误。 主要功能: 启动多个 Goroutine,并等待它们执行完毕。 如果有任何一个 Goroutine 返回错误,errgroup 会立刻停止其他 Goroutine 的执行(可以选择是否停止)。 收集并返回第一个错误(如果有的话)。 关键方法: Go(func() error):启动一个新的 Goroutine 并指定该 Goroutine 的错误返回值。 Wait():等待所有 Gorou.... 认识errgroup go
“原语”(Primitive)在计算机科学中的意思是指最基本的操作或构建块,它们不能再被分解成更小的部分。在编程中,原语通常是最基础、最底层的操作,用来构建更复杂的程序和功能。 通俗解释: 想象你在做拼图,每个拼图块就是一个“原语”。这些拼图块无法再拆分成更小的部分,它们是构成大拼图的最基本单位。通过组合这些基本拼图块,你才能做出完整的拼图。 计算机中的“原语”: 在计算机编程中,原语通常指的是某种语言提供的最简单、最基础的操作。这些操作是程序运行的基本单元。比如: 数字加法:加法运算(如 1 + 2)是最基本的计算操作。 赋值操作:像 x = 5 就是一个原语,它把值 5 赋给变量 x。 比较操作:像 ==、> 等符号表示比较两个值。 这些操作在计算机内部通常是由硬件支持的,通常不会再分解成更小的部分,因此它们叫做“原语”。 编程语言中的原语: 编程语言的原语有时也包括一些数据结构或控制结构,这些都是基础操作,无法再拆分。例如: 数组和列表是很多编程语言提供的原始数据结构。 循环(如 for、while)和条件判断(如 if、switch)是程序中最基础的控制结构。 为什么重要.... 理解什么是"原语" 计算机
在 Go 语言中,常见的同步原语有以下几种: 1. sync.Mutex(互斥锁) Mutex 是最常用的同步原语之一,用于保证同一时间只有一个 Goroutine 能够访问某个共享资源。它有两个主要方法: Lock():锁定互斥量,直到它被解锁。 Unlock():解锁互斥量,允许其他 Goroutine 锁定它。 示例: package main import ( "fmt" "sync" ) var ( mutex sync.Mutex counter int ) func increment() { mutex.Lock() // 锁定互斥量 defer mutex.Unlock() // 确保解锁 counter++ } func main() { var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() increment() }() } wg.Wait() fmt.Println("Counter:", counter) } 2. sync.RWM.... go的同步原语 go
把 HTTPS 改成 HTTP 后,带宽减少了 70%,这个现象主要是由以下几个原因引起的: 1. HTTPS 增加的加密开销 加密与解密:HTTPS 是基于 TLS(传输层安全)协议的,它在通信过程中会对数据进行加密和解密。这意味着每一次请求和响应都需要进行额外的计算和内存消耗,用来加密发送的数据和解密接收到的数据。由于加密和解密操作消耗一定的计算资源,HTTPS 请求在传输数据时通常会比 HTTP 更加复杂。 TLS 握手:HTTPS 在每次建立连接时需要进行一次 TLS 握手,这个过程涉及到密钥交换、证书验证等操作,需要额外的数据交换和计算开销。尤其是对于非持久连接(每次请求建立新连接的情况),握手过程会增加额外的带宽消耗。 2. 数据包大小 HTTP 直接传输明文数据,没有加密和签名过程,因此数据本身相对较小。 HTTPS 会将数据加密,这意味着传输的每个数据包通常会比相同的 HTTP 数据包要大。加密的数据包通常包含了额外的元数据,例如填充数据、加密的头信息等,这些都导致了加密后数据包的大小增大,尤其是在高频繁的小数据包的请求中,这个开销会比较显著。 例如: HTTP 请求 .... 有更新! 把 https 改成 http带宽减少了 70% http
在 Go 语言中,SingleFlight 是 golang.org/x/sync/singleflight 包中的一个工具,旨在避免对同一资源或任务发起重复的请求。它的主要作用是合并多个相同的并发请求,使得这些请求只会执行一次,并且共享相同的结果,从而减少系统的负载,避免重复的工作。 主要用途 SingleFlight 主要用于场景中,当多个 goroutine 发起对同一资源或任务的重复请求时,只会有一个请求被真正处理,其他请求会等待这个请求完成,并共享相同的结果。 典型应用场景 缓存穿透:当多个请求访问相同的缓存键,而该键的值未命中缓存时,多个并发请求会导致重复的计算或数据库查询。SingleFlight 可以通过合并这些请求,只执行一次查询,其他请求则等待并返回相同的结果。 网络请求合并:多个相同的网络请求可能会同时到达,可以合并这些请求,减少请求次数。 SingleFlight 的工作原理 SingleFlight 通过维护一个正在进行中的请求的集合来避免重复请求。 当多个请求到达时,SingleFlight 会把它们合并到一个请求上,只有一个请求会被真正执行。 其他请求会等.... 认识SingleFlight合并请求 go
在使用 gorm 进行数据库操作时,Find 和 Scan 都是用来查询数据的,但它们有一些重要的区别,特别是在查询的结果存储和数据类型匹配方面。 1. Find 方法 Find 是 gorm 中常用的查询方法,用于获取查询结果,并将结果映射到一个结构体或结构体切片中。Find 会根据模型的字段来映射查询结果,因此通常会返回完全匹配的字段。 示例: var users []User db.Find(&users) // 查询所有用户 在这个例子中,Find 会查询所有用户,并将查询结果存储到 users 切片中。User 结构体的字段会与数据库表的列进行映射。如果表中存在额外的字段或字段类型不匹配,gorm 会自动进行处理。 特点: Find 会自动将数据库中的列名与结构体字段名匹配,使用反射将数据库字段填充到结构体中。 如果结果列与结构体字段的类型不匹配,gorm 会进行类型转换。 如果查询结果不包含某些字段,它会使用结构体中默认的零值填充。 Find 主要用于查询数据并将结果映射到结构体中,通常不需要额外处理。 Find 适用场景: 当你已经有一个结构体,并且希望将查询结.... gorm Find()与Scan()的区别 gorm
pkg/errors 是 Go 语言中的一个第三方库,主要用于改进错误处理。它提供了对 Go 原生错误处理的一些增强,比如支持堆栈追踪、错误的上下文信息附加、错误的包裹等。通过 pkg/errors,你可以更加清晰地了解错误的来源以及错误链的详细信息。 这个库最著名的特性是通过 Wrap 和 WithStack 方法来为错误附加堆栈信息,帮助开发者调试和定位错误源。 1. 安装 pkg/errors 首先,确保你的 Go 环境已经配置好了,并且安装了 pkg/errors 库。你可以通过以下命令来安装: go get github.com/pkg/errors 2. 核心结构体:errorString pkg/errors 的核心是通过 error 接口来处理错误的,但它有几个增强的结构体,比如 stack 和 withMessage,这使得它在错误处理中提供了更强大的功能。 首先我们来看下 errors.go 文件中的一部分代码,看看库的核心结构。 package errors import ( "fmt" "runtime" "strings" ) // errorString .... 初识pkg/errors go
golang-set 是一个常用的 Go 语言实现的集合(Set)数据结构库。集合是一个包含多个唯一元素的无序容器。在 Go 中,标准库并没有提供集合类型,因此很多开源库实现了集合的功能,golang-set 就是其中之一。它为 Go 提供了一个简单的实现,使得可以使用集合的功能,比如去重、并集、交集、差集等。 golang-set 简介 golang-set 是一个线程安全的集合类型,基于 Go 语言的内建 map 实现。它提供了一个高效且易于使用的集合 API,支持常见的集合操作(如添加、删除、检查元素等)。 该库的 GitHub 仓库地址为:https://github.com/deckarep/golang-set 以下是源码的基本结构和一些关键功能的分析。 1. 主要结构体:Set type Set struct { m map[interface{}]struct{} } Set 结构体是 golang-set 的核心,包含一个字段 m,它是一个 map 类型,存储集合的元素。集合的元素被存储为 interface{} 类型,这样就可以存储任何类型的元素。而结构体的值是一.... 初识golang-set go
文件描述符表的限制 每个进程在 Linux 系统中都拥有一个文件描述符表,文件描述符(File Descriptor, FD)是进程与操作系统之间管理和访问文件、设备、套接字等 I/O 资源的一个抽象接口。由于操作系统资源有限,每个进程可同时打开的文件描述符数量是有限制的。这个限制是操作系统内核的一部分,并且会受到多个因素的影响。 1. 文件描述符的最大限制 在 Linux 系统中,每个进程可以打开的文件描述符的最大数量由内核设置。这个限制可以通过操作系统的配置进行查看和调整。文件描述符的最大值与系统的资源、内核的配置以及安全策略等因素密切相关。 1.1 默认的文件描述符限制 每个进程默认可以打开的文件描述符数量通常是有限制的。在大多数 Linux 系统中,这个默认限制通常为 1024。这意味着每个进程最多只能打开 1024 个文件、套接字、管道等文件描述符。 1.2 硬限制与软限制 Linux 提供了两个级别的文件描述符限制: 软限制(Soft Limit):这是当前进程允许打开的文件描述符的数量。通常情况下,软限制可以被普通用户通过 ulimit 命令修改,但如果达到硬限制,则无法.... 修改文件描述符的最大限制 linux
进程的文件描述符表 在 Linux 系统中,文件描述符表是每个进程用于管理文件描述符(File Descriptors,简称 FD)的一种数据结构。每个进程都有一个独立的文件描述符表,用于记录该进程打开的文件或输入输出资源(如管道、套接字、设备等)以及与之相关的信息。通过文件描述符,进程可以执行各种 I/O 操作,如读取、写入文件,或通过套接字进行网络通信。 1. 文件描述符表的结构 文件描述符表是一个内存中的数据结构,其中每个条目(通常是一个结构体)表示一个打开的文件或 I/O 流。每个文件描述符都是文件描述符表中的一个索引,用于访问对应的文件或资源。 进程的文件描述符表:每个进程都有一个文件描述符表(File Descriptor Table),它包含指向内核中打开文件的指针。这些指针指向了内核内部的数据结构,如文件描述符结构体(struct file)和文件操作结构体(struct file_operations)。 文件描述符表中的条目:每个条目包括文件描述符指向的文件或资源的相关信息,如: 文件的位置(文件指针,记录文件的读写位置)。 文件状态(只读、只写、读写等)。 引用.... 进程的文件描述符表 linux
Linux 文件描述符 (File Descriptor) 在 Linux 操作系统中,文件描述符(File Descriptor,简称 FD)是一个非负整数,用于标识一个已经打开的文件或输入输出资源(如管道、套接字、终端等)。它是 Linux 系统中对文件操作的抽象表示。每当程序打开一个文件或创建一个流时,操作系统会分配一个文件描述符来管理这个文件或流。 1. 文件描述符的基本概念 每个进程在 Linux 系统中都有一个文件描述符表。文件描述符表保存了进程打开的文件与内核管理的文件对象之间的映射关系。 文件描述符的类型: 普通文件:磁盘文件。 设备文件:如 /dev/sda。 目录文件:如 /home/user。 套接字:网络通信的接口。 管道:进程间通信。 文件描述符与文件描述符表: 操作系统会为每个进程维护一个文件描述符表。文件描述符表用于管理进程打开的文件和 I/O 流。文件描述符就是这个表中的索引。 2. 标准文件描述符 在 Linux 中,每个进程默认会打开 3 个标准文件描述符: 标准输入(stdin):文件描述符为 0,通常与键盘关联。 标准输出(stdout):文件.... linux中的文件描述符 linux
在 Go 语言中,map 是一种内置的数据类型,用于存储键值对。Go 中的 map 类型是哈希表的实现,它可以让你通过键快速查找、插入、删除对应的值。下面是对 Go 语言 map 数据结构的源码和实现原理的深入解析。 1. Go map 源码结构 Go 语言的 map 内部实现的源码位于 Go 标准库的 runtime 包中,具体的 map 实现代码可以在 runtime/hashmap.go 文件中找到。Go 的 map 是一个非常复杂的内存管理结构,涉及了内存分配、哈希表的冲突解决、键值的存储方式等。 以下是 Go map 的一个简化版本的实现原理。 2. Go map 的数据结构 map 的底层数据结构是一个哈希表,包含以下几个关键组件: 桶(Bucket):每个桶包含多个键值对,桶的数量和每个桶能存储的键值对数量是固定的。桶的数量会随 map 的大小自动扩展。 哈希函数:通过哈希函数,将键映射到哈希表中的某个桶。Go 使用了改进的哈希函数,以减少冲突的可能性。 扩容机制:当 map 的元素数目增加时,它会触发扩容(rehash),扩容时,Go 会重新计算每个键的哈希值并将它们放.... golang之map源码 go
在 Go 语言的 Gin 框架中,gin.Context 和 context.Context 都是与请求处理相关的上下文对象,它们在某些场景下会有交集,但也有各自的特点和用途。 1. gin.Context 和 context.Context 的关系与区别 context.Context context.Context 是 Go 标准库中定义的一个接口,用于在 goroutine 间传递取消信号、超时、截止时间和请求范围的数据。它是 Go 并发编程中非常重要的工具。 用途:context.Context 是一个轻量级的上下文对象,用于跨函数调用传递信息,特别是用于取消信号和超时控制等。 // 在 Go 标准库中的 context 包 type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} } gin.Context gin.Context 是 Gin 框架自定义的类.... gin.Context与context.Context相互转换 gin
在 Go 语言中,context 包提供了一种在多个 goroutine 之间传递取消信号和请求作用域(如超时、截止时间等)的机制。context 在并发编程中扮演着重要角色,尤其是在处理 HTTP 请求、数据库操作等需要在多个 goroutine 间共享和传递状态的场景中。 1. context 的用途 取消信号:可以通知多个 goroutine 停止工作,避免无用的资源消耗。 超时控制:可以为一系列操作设置一个截止时间,超过这个时间自动取消。 请求范围数据:可以在上下游 goroutine 间传递数据,通常用于传递请求 ID、认证信息等。 2. context 的基本概念 context 包中的主要概念是 Context 类型,它是一个接口,定义了用于取消信号、截止时间、超时和传递数据的方法。Context 实现是不可变的,每次创建新 Context 时会基于现有的 Context 扩展出新的状态。 3. context 的常见函数和方法 context.Background():返回一个空的、根 Context,通常用于整个程序的入口。 context.TODO():返回一个空的.... golang之context go
sync.Map 是 Go 标准库 sync 包中的一个并发安全的映射(map)类型。它是为了解决普通 map 在多 goroutine 并发访问时的竞态问题而设计的。sync.Map 提供了更高效的并发操作,避免了显式使用锁(如 sync.Mutex)来保护 map 的访问。 特点 并发安全:sync.Map 支持并发读写,保证多 goroutine 同时对 map 的读写操作不会发生竞态条件。 性能优化:它通过内部的 atomic 操作来提高性能,尤其是对读操作比较频繁的场景,它的性能通常优于使用显式锁的 map。 不支持直接索引:与普通的 map 不同,sync.Map 不支持使用普通的下标方式来进行访问(即 syncMap[key] 不可用)。你需要使用专门的 API 来进行操作。 sync.Map 的主要方法 sync.Map 提供了以下几个方法: Store(key, value):存储一个键值对。 Load(key):读取一个值,如果键存在,返回该值和 true,否则返回 nil 和 false。 LoadOrStore(key, value):如果键不存在,则将 key.... golang之sync.Map go
Go 的 sync 包提供了多个用于同步并发操作的工具,主要包括互斥锁(Mutex)、等待组(WaitGroup)、读写锁(RWMutex)、一次性操作(Once)、条件变量(Cond)等。这些工具使得在多协程并发环境下的共享资源访问更加安全。 下面详细介绍 sync 包中的常用类型和它们的使用。 1. sync.Mutex(互斥锁) Mutex(互斥锁)用于控制对共享资源的访问,确保在同一时刻只有一个 goroutine 可以访问该资源。 示例 package main import ( "fmt" "sync" ) var mu sync.Mutex var counter int func increment() { mu.Lock() // 上锁 defer mu.Unlock() // 解锁 counter++ } func main() { var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() increment() }() } wg.Wait().... 初识golang之sync包 go
json 和 jsoniter 都是 Go 语言中常用的 JSON 序列化/反序列化库。它们的功能相似,都是用来处理 JSON 数据的编码和解码,但它们在性能、功能、兼容性等方面有所不同。下面我会详细介绍它们之间的区别以及各自的特点。 1. encoding/json(Go 内置的 JSON 库) encoding/json 是 Go 标准库提供的 JSON 序列化和反序列化的工具。它是 Go 内置的,因此不需要额外安装,使用非常方便。它的主要功能是: Marshal:将 Go 对象(如结构体、数组、切片等)编码为 JSON 字符串。 Unmarshal:将 JSON 字符串解码为 Go 对象。 示例代码(encoding/json) package main import ( "encoding/json" "fmt" "log" ) type User struct { Name string `json:"name"` Age int `json:"age"` Email string `json:"email"` } func main() { // 创建结构体 user :=.... json与jsoniter json
在 Gin 框架中,路径参数是通过在 URL 路径中定义动态部分来传递数据的。这些动态部分通常使用冒号(:)表示。在请求到达时,Gin 会将这些动态路径部分提取出来,并赋值给对应的参数。 1. 路径参数的基本使用 路径参数可以在路由定义时使用 : 来标记。通过 c.Param("param_name") 方法获取路径参数的值。 示例代码 package main import ( "fmt" "github.com/gin-gonic/gin" ) func main() { // 创建 Gin 路由 r := gin.Default() // 定义路由,使用路径参数 r.GET("/user/:id", func(c *gin.Context) { // 获取路径参数 id := c.Param("id") c.JSON(200, gin.H{ "user_id": id, }) }) // 启动服务器 r.Run(":8080") } 在上述代码中: r.GET("/user/:id", func(c *gin.Context)) 定义了一个带有路径参数的路由,其中 :id 就是.... gin之路径参数 gin
在 Go 中,Redis Hook 主要是指通过 Redis 客户端库(如 go-redis)的钩子函数来扩展或修改 Redis 操作的行为。这些钩子可以让你在 Redis 操作的不同生命周期阶段插入自定义逻辑,例如在连接 Redis 时记录日志、对 Redis 命令执行前后做一些操作等。 go-redis 是 Go 语言中常用的 Redis 客户端库,它提供了多个钩子(hooks)功能,常见的有以下几种: 连接钩子:在连接 Redis 时执行一些操作。 命令钩子:在执行 Redis 命令前后执行某些操作。 关闭钩子:在关闭连接时执行清理工作。 1. go-redis Hook 概述 go-redis 提供了 OnConnect 和 OnClose 钩子,可以在 Redis 连接的生命周期中执行特定操作。同时,它还提供了 Command Hook,使你可以在 Redis 命令执行前后插入一些自定义逻辑。 连接钩子 OnConnect OnConnect 是一个钩子函数,当 Redis 客户端连接到 Redis 服务器时会调用它。这可以用于日志记录、监控等。 命令钩子 BeforePro.... go redis(v8) hook redis
在 Gin 框架中,Request ID 是用于标识每个请求的唯一标识符。它通常用于追踪日志、请求链路、监控、错误跟踪等场景,确保在分布式系统中对请求的追踪更加清晰。每个请求都有一个唯一的 Request ID,它可以帮助我们在日志中追溯整个请求的处理流程,尤其是在微服务架构中,当请求跨越多个服务时,Request ID 的存在能够帮助我们轻松关联各个服务间的日志。 1. 为什么需要 Request ID 日志追踪:通过在日志中记录 Request ID,可以确保多个日志条目属于同一个请求,从而便于错误追踪和日志分析。 服务间追踪:在微服务架构中,Request ID 能够帮助我们将不同服务处理同一个请求的日志关联起来。 错误处理:在请求失败时,Request ID 可以帮助开发人员快速定位问题,并且在支持故障恢复和排查时非常有用。 分布式追踪:Request ID 可以作为请求的标识符,帮助实现分布式追踪,如与 Jaeger、Zipkin 等系统集成时,Request ID 作为 Trace ID 在多个服务之间传递。 2. 如何在 Gin 中实现 Request ID 我们可以通过.... gin中间件之RequestID gin