版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引言 今天萌叔翻看Golang的request库go-resty/resty,看了请求trace Info的相关的细节。 程序如下: package main import ( “fmt” “github.com/go-resty/resty/v2” ) func main() { target := “https://vearne.cc/” client := resty.New() resp, err := client.R(). EnableTrace(). Get(target) if err != nil { fmt.Println(“error:”, err) return } fmt.Println(len(resp.String()), resp.StatusCode()) // Explore trace info… 继续阅读 go-resty/resty中Trace Info说明

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 本文基于Golang 1.17.6 1.前言 之前萌叔曾在文章 imroc/req 连接池使用须知 提及过Golang标准库net/http提供的连接池http.Transport,但是是浅尝辄止。 本文萌叔想从http.Transport出发来谈谈一个连接池设计应该考虑哪些问题? 2.连接池的功能特征 下图是针对Grpc和Thrift压测结果(见参考资料1)。我么可以看出,长连接相比与短连接相比,QPS大概提升了1倍多。这是因为长连接减少连接建立的所需的3次握手。 要对长连接进行管理,特别是对闲置的长连接进行管理,就不可避免的引入连接池。 特征 需要一个连接时,并不一定真的创建新连接,而是优先尝试从连接池选出空闲连接;如果连接池对应的连接为空,才创建新连接。 销毁并不是真的销毁,而是将使用完毕的连接放回连接池中(逻辑关闭)。 这里引出了几个问题。 Q1:获取连接阶段,我们有没有办法知道从连接池中取出的空闲连接(复用)是有效的,还是无效的? Q2:把使用完毕的连接放回连接池的阶段,空闲连接数量是否要做上限的约束。如果空闲连接数量有上限约束且空闲连接的数量已经达到上限。那么把连接放回连接池的过程,必然需要将之前的某个空闲连接进行关闭,那么按照什么规则选择这个需要关闭的连接。 Q3:放置在连接池中的连接,随着时间的流逝,它可能会变成无效连接(stale)。比如由于Server端定时清理空闲连接。那么为了确保连接池中连接的有效性,是否需要引入定时的检查逻辑? 3.net/http中连接池的实现 net/http中连接池的实现代码在 net/http/transport.go 中 获取连接 Transport.RoundTrip() -> Transport.getConn() 放回连接(逻辑关闭) Response.Body.Close() -> bodyEOFSignal.Close() -> Transport.tryPutIdleConn() 为了约束空闲连接的数量,连接池引入了几个变量: MaxIdleConns MaxIdleConns controls the maximum number of idle… 继续阅读 从http.Transport看连接池的设计

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 本文基于go1.17.6 1.什么是协程? 协程,英文Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』, 具有对内核来说不可见的特性。 1.1 协程的特点 线程的切换由操作系统负责调度,协程由用户自己进行调度 线程的默认Stack大小是MB级别,而协程更轻量,接近KB级别。 在同一个线程上的多个协程,访问某些资源可以不需要锁 适用于被阻塞的,且需要大量并发的场景。 1.2 Golang的GMP模型 CPU驱动线程上的任务执行 线程由操作系统内核进行调度,Goroutine由Golang运行时(runtime)进行调度 P的 local runnable queue是无锁的,global runnable queue是有锁的 P的 local runnable queue长度限制为256 注意: M和P是绑定关系 M和G是绑定关系 P只是暂存G,他们之间不是绑定关系 E-R图 简化后的E-R图 注意: 后面为了书写简单直接将 local runnable queue表示为本地队列 global runnable queue表示为全局队列 延伸 timer的四叉堆和内存分配器使用的mcache也是每个P一个 Q:… 继续阅读 Golang协程调度

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 注意: 本文基于Golang 1.17.1 1.引言 阅读Golang的源码的话,会发现里面有形形色色的同步原语,发挥着重要的作用。这篇文章,萌叔打算把它们之间的依赖关系,以及与Linux的同步原语的对应关系整理一下。 2. 与Linux系统的同步原语之间的对应关系 锁类型 Golang Linux 互斥锁 runtime.mutex (低阶) sync.Mutex (高阶) #include <pthread.h>int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_trylock(pthread_mutex_t *mutex);int pthread_mutex_unlock(pthread_mutex_t *mutex); 信号量 runtime.semaphore #include <sys/sem.h>int semget(key_t key, int nsems, int semflg);int semctl(int semid, int semnum, int cmd, …);int semop(int… 继续阅读 聊聊Golang中形形色色的同步原语

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引言 Golang在Timer(计时器)的实现中使用了四叉堆,而不是常见的二叉堆,这个问题引起了我的兴趣。 对于这个问题,网上的解释是这样的 但是我仔细查阅了资料以后,有了点有趣的发现。 2.说明 假设: 堆为d叉堆,堆中共有n个节点 备注: 时间复杂度的证明见d-ary heap Timer最常见的2个操作 1)Insert() 添加一个计时器。涉及 sift-up(上推)操作,时间复杂度为O(log n / log d) Python代码 import math result = math.log(n, d) 时间消耗(需要交换或者比较的次数) n(数量) 二叉堆 四叉堆 八叉堆 1w 13.3 6.64 4.43 10w 16.61 8.30 5.54 100w 19.93 9.97… 继续阅读 为什么Golang的Timer实现使用四叉堆而不是二叉堆

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 本文是一篇对mock库的小结。 2. 怎么理解mock这件事 2.1 为什么需要mock 最常见的场景是,一个服务B和它依赖的上游微服务A同时启动开发。为了加快开发速度,服务B不可能等到服务A开发完,在启动开发。因此服务A和服务B约定它们之间交互的接口,然后就可以启动开发了。 服务B为了验证自身的业务逻辑,它需要mock一个A服务(只需要mock相关接口)。但是mock A服务本身可能也比较麻烦,涉及到网络IO,端口占用等等。所以进一步的,我们只需把与服务A的交互封装到一个interface中。这样只需要mock这个interface就可以了。 2.2 示例 mock最常见的场景是用于单元测试 完整示例 biz.go type People interface { Say(s string) string } // 业务逻辑 func BizLogic(p People, s string) string { return “People Say:” + p.Say(s) } 在单元测试中,验证我们的业务逻辑 biz_test.go import… 继续阅读 Golang常见mock库小结

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.前言 在上一篇文章读写锁为什么那么快?(1)中,萌叔探讨了读写锁提高程序性能的原因。这一篇文章,萌叔将聊聊读写锁面临的2个问题。并看看标准库的是如何实现。 2.几个问题 Q1: 如何避免写锁饥饿或者读锁饥饿问题? 我们知道读写锁的特性:同一时刻允许多个线程(协程)对共享资源进行读操作, 也就是说多个线程(协程)可以多次加读锁。那么会出现这样一种情况,读锁还没有被完全释放的情况(读锁计数器大于0),又有新的读操作请求加读锁。这样写协程永远无法加上写锁,将会一直阻塞。 A1: 标准库实际上引入某种类似排队的机制 如上图所示,协程G1、G2、G3、G4并发访问共享资源。它们请求加锁的顺序为矩形的左边沿,耗时为矩形块的长度 t1时刻,G1加上了读锁,开始读操作 t2时刻,G2也加上了读锁,进行读操作 t3时刻,G3表示它要进行写操作 t4时刻,G4也想加读锁,并且现在共享资源上施加的读锁还没有完全释放(需要到t5时刻才能释放),但是由于G3已经表示它需要加写锁了,所以G4将会阻塞 所以实际的运行顺序是 Q2: 如何确保加读锁时,成本尽可能的低? 读写锁最适用的情况是读多写少的场景,降低读锁的加锁和释放锁的开销,才能提高整体的吞吐能力。 A2: 读协程和写协程互斥仅用的到int32的整数 萌叔虽然也使用condition实现了读写锁,但由于 condition要求在判断条件是否满足,以及修改条件中相关联的变量时,都需要加互斥锁,因此萌叔的实现性能和标准库有一定的差距。 写协程和写协程的互斥靠的是标准库的互斥锁 3.代码 标准库的实现很巧妙,阅读有一定的难度。萌叔将对部分代码进行注释,并讲解。 package sync import ( “internal/race” “sync/atomic” “unsafe” ) type RWMutex struct { w Mutex // held… 继续阅读 读写锁为什么那么快?(2)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.实验 首先让我们来看一组单元测试结果,看看互斥锁和读写锁在不同场景下的表现。 这里使用了极客兔兔的《Go 语言高性能编程》中的case,在此特别鸣谢 vearne/golab BenchmarkReadMore-4 1 1195229024 ns/op BenchmarkReadMoreRW-4 9 122810085 ns/op BenchmarkReadMoreMyRW-4 8 145523134 ns/op BenchmarkWriteMore-4 1 1296582090 ns/op BenchmarkWriteMoreRW-4 1 1194438009 ns/op BenchmarkWriteMoreMyRW-4 1 1247918412 ns/op BenchmarkEqual-4 1 1344248968 ns/op BenchmarkEqualRW-4 2 661674534 ns/op BenchmarkEqualMyRW-4 2 685353924 ns/op… 继续阅读 读写锁为什么那么快?(1)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 读者们可能使用过 fullstorydev/grpcurl 来对grpc服务进行调用调试。 假定有一个简单的grpc的服务 main.go package main import ( “context” “log” “net” “google.golang.org/grpc” pb “google.golang.org/grpc/examples/helloworld/helloworld” “google.golang.org/grpc/reflection” ) const ( port = “:50051” ) // server is used to implement helloworld.GreeterServer. type server struct { pb.UnimplementedGreeterServer } // SayHello implements… 继续阅读 grpc的反射机制

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 萌叔有有一个开源项目vearne/passwordbox,用于密码的管理。笔者想法是在Mac上build出多个平台下的bin文件,这样用户,可以无需自己编译,直接使用编译好的bin文件。 可是 vearne/passwordbox 内部依赖了 mattn/go-sqlite3。这个库编译时,依赖操作系统上的共享库,无法直接进行交叉编译。 2. 解决 前段时间,萌叔在阅读buger/goreplay源码时,偶然发现它有一个思路是利用docker来实现Golang交叉编译。于是笔者借鉴了它的思路,修改了passwordbox Makefile。 现在在Mac上执行 make docker-img # 只需要执行一次,生成基础镜像即可 make release 就可以同时生成Mac和linux下的bin文件 pwbox-v0.0.10-darwin-amd64.tar.gz pwbox-v0.0.10-linux-amd64.tar.gz 完整代码见Makefile 2.1 build一个基础镜像用于linux环境的编译 docker-img: docker build –rm -t $(CONTAINER) -f Dockerfile.dev . Dockerfile.dev FROM golang:1.14 RUN apt-get update && apt-get… 继续阅读 利用docker实现Golang程序的交叉编译