记录常用的各种工具
用于Go项目 名称 描述 gopkgview 交互式的基于Web的Go包依赖图可视化 gup 更新由go install安装的二进制文件 gsa 分析编译后的Go二进制文件大小的工具,可以清晰的了解每个包或依赖在二进制中占用的大小 mockgen gomock是Go编程语言的模拟框架 nilaway 静态分析工具,用于检测Go代码中的潜在nil恐慌 命令行 名称 描述 bat cat的现代替代品 curlie curl的力量,httpie的使用便捷性 croc 轻松安全地将内容从一台计算机发送到另一台计算机 difft 结构化差异,理解语法。也可用于git diff direnv 加载或卸载当前目录(以及目录)的.envrc或.env fastfetch 功能丰富、面向性能、类似 neofetch 的系统信息工具 fd find的现代替代品 fzf 命令行模糊查找器 grpcurl 与cURL类似,但针对gRPC:与gRPC服务器交互的命令行工具 htop top的替代品。一个交互式流程查看器 hyperfine 命令行基准测试工具 jq 命令行JSON处理器 lsd ls的现代替代品 mycli MySQL自动补全和语法高亮的终端客户端 ouch 支持多种格式的压缩和解压缩,tar、zip、7z、gz、rar等 pgcli Postgres自动补全和语法高亮的终端客户端 rg 一个面向行的搜索工具,它递归地搜索当前目录中的正则表达式模式 scc 一个类似于cloc、sloccount和tokei的工具。用于统计多种编程语言的代码行数、空白行、注释行和源代码的物理行数 sd sed的现代替代品 vivid 具有丰富文件类型数据库的主题化LS_COLORS生成器 zoxide 更智能的cd命令,支持jump App 名称 描述 iina 适用于macOS的现代视频播放器 mac-mouse-fix macOS平滑滚动
十亿行挑战
十亿行挑战(1️⃣🐝🏎️ The One Billion Row Challenge) 原始仓库 文中源码仓库: https://github.com/zzhaolei/1brc 目标 文本文件包含了一系列气象站的温度值。每行是一个测量值,格式为<string: station name>;<double: measurement>,其中测量值精确到一位小数。以下是一些示例行: 1 2 3 4 5 6 7 8 9 10 Hamburg;12.0 Bulawayo;8.9 Palembang;38.8 St. John's;15.2 Cracow;12.6 Bridgetown;26.9 Istanbul;6.2 Roseau;34.4 Conakry;31.2 Istanbul;23.0 任务是编写一个程序,该程序读取文本文件,计算每个气象站的最低、平均和最高温度值,并将结果输出到stdout, 格式如下(按气象站名称字母顺序排序,并且每个气象站的结果值格式为<min>/<mean>/<max>,保留一位小数点): 1 {Abha=-23.0/18.0/59.2, Abidjan=-16.2/26.0/67.3, Abéché=-10.0/29.4/69.0, Accra=-10.1/26.4/66.4, Addis Ababa=-23.7/16.0/67.0, Adelaide=-27.8/17.3/58.5, ...} 限制 只能使用标准库实现。 生成十亿行挑战所需的数据 克隆原始仓库: 1 2 3 git clone https://github.com/gunnarmorling/1brc cd 1brc/src/main/python python3 create_measurements.py 1000000000 生成的数据会在1brc/measurements.txt,约为15Gi的大小。 ...
Go 在 Mac 或 Linux 上构建动态库
Go 可以导出 C ABI,然后在其它兼容 C ABI 的语言中调用。 下面详细讲解一下用法: Go 构建动态库 定义一个 go 文件,包含以下代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package main import "C" //export Add func Add(a, b int) int { return a + b } //export Multiply func Multiply(a, b int) int { return a * b } func main() {} go 的文件名称在当前示例中无关紧要,这里定义为 main.go。 main 函数是必须的,但是可以为空。 注意:代码中的 //export Add 表示导出 Add 函数,export 和 // 之间没有空格。这是 Go 中的一种特殊指令,类似的还有 //go:build 等。 ...
Rust main 函数是如何被执行的
Rust的main函数到底是如何被执行的呢? 让我们看一个关于main函数的示例: 1 2 3 4 5 6 use std::error::Error; fn main() -> Result<(), Box<dyn Error>> { println!("hello world"); Ok(()) } 从这个示例我们可以看到,rust的main函数竟然还可以返回Result枚举,这是为什么?rust到底是如何执行用户定义的main函数的呢? 接下来让我们对rust的源码进行剖析,看一看rust到底是如何运行main函数的。 Rust 运行时 首先,在几乎所有的语言中(目前我不知道哪个语言会不进行处理),在执行用户的main函数之前都需要进行一些初始化工作,比如分配堆栈、创建并绑定主线程、初始化通用寄存器、初始化GC等等。 而rust也不例外,也会在实际调用用户执行的main之前进行一些初始化的操作。 你没看错,rust也是有运行时的,只不过这个运行时没有GC,非常的轻量级,主要是执行上面所说的初始化操作以及对main函数的执行和收尾。 让我们先从init开始: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // 在执行 main 之前执行 unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { #[cfg_attr(target_os = "teeos", allow(unused_unsafe))] unsafe { // 实际的资源初始化逻辑 sys::init(argc, argv, sigpipe) }; // 设置主线程,并设置一个名字 let thread = Thread::new_main(); thread::set_current(thread); } // 运行时只会执行一次 cleanup。 // 在 main 或程序退出的时候执行 // NOTE: 当程序被终止的时候,不能保证执行 cleanup // (终止是 kill 等强制终止,或段错误等行为,程序无法继续执行,资源由操作系统进行回收) pub(crate) fn cleanup() { static CLEANUP: Once = Once::new(); CLEANUP.call_once(|| unsafe { // 刷新 stdout 缓冲区的数据,并禁用缓冲区 crate::io::cleanup(); // SAFETY: 通过 Once 保证,只会执行一次 cleanup sys::cleanup(); }); } 系统资源的初始化和清理在 sys::init 和 sys::cleanup 中 sys::init 在ffi中不保证被调用 sys::init 的源码不是算复杂,主要是保证打开标准输入输出流、初始化栈,感兴趣的可以自行阅读源码 现在我们终于可以进入重点了,rust对main函数的处理逻辑: ...
使用AWS S3 SDK访问阿里云oss
目前业务上使用的是 aws 的 s3 服务,但是想兼容阿里云的 oss。根据oss的文档描述,oss支持使用 aws 的 sdk 进行访问,所以记录一下处理流程 访问AWS S3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package main import ( "context" "log" "os" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3" ) func NewS3Client() *s3.Client { accessKeyID := os.Getenv("ACCESS_KEY_ID") accessKeySecret := os.Getenv("ACCESS_KEY_SECRET") cfg, err := config.LoadDefaultConfig( context.TODO(), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKeyID, accessKeySecret, "")), config.WithEndpointResolverWithOptions( aws.EndpointResolverWithOptionsFunc(func(_, _ string, _ ...interface{}) (aws.Endpoint, error) { return aws.Endpoint{ PartitionID: "aws-cn", URL: "https://s3.cn-northwest-1.amazonaws.com.cn", SigningRegion: "cn-northwest-1", }, nil }), ), ) if err != nil { log.Fatal(err) } return s3.NewFromConfig(cfg, func(o *s3.Options) { // 此选项可用于调试 // o.ClientLogMode = aws.LogSigning | aws.LogRequest | aws.LogResponseWithBody o.UsePathStyle = true }) } func main() { bucket := os.Getenv("S3_BUCKET") uploadKey := os.Getenv("S3_KEY") file, _ := os.Open("test.txt") client := NewS3Client() _, err := client.PutObject(context.Background(), &s3.PutObjectInput{ Bucket: aws.String(bucket), Key: aws.String(uploadKey), Body: file, }) if err != nil { log.Fatal(err) } } 这是一个简单的 s3 文件上传,通过在 PutObjectInput 中指定Bucket 参数的形式。 ...
通过docker配置MySQL主从服务
目录结构 1 2 3 4 5 6 . ├── master │ └── my.cnf ├── slave │ └── my.cnf └── docker-compose.yml master:主配置 slave:从配置 docker-compose:通过docker-compose进行容器配置和启动 master/my.cnf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # For advice on how to change settings please see # http://dev.mysql.com/doc/refman/8.3/en/server-configuration-defaults.html [mysqld] host-cache-size=0 skip-name-resolve datadir=/var/lib/mysql socket=/var/run/mysqld/mysqld.sock secure-file-priv=/var/lib/mysql-files user=mysql pid-file=/var/run/mysqld/mysqld.pid # 自定义部分 log-bin=master-bin binlog-format=row # row 按行重放,statement 重放 sql 语句,mixed 默认基于 statement,一旦发现基于 sql 无法精准重放时,会使用 row,MySQL 默认是基于 statement 的复制 binlog-do-db=test # 开启 binlog 的数据库名,如果有多个数据库,那么可以重复设置 server-id=1 # server-id 不能和任何 主或从 重复 # 自定义部分 [client] socket=/var/run/mysqld/mysqld.sock !includedir /etc/mysql/conf.d/ slave/my.cnf 和master/my.cnf内容基本一致,但是server-id不能重复 ...
Go设计模式——单例模式
介绍 单例模式同时解决了两个问题: 保证一个类只有一个实例,例如控制某些共享资源(如数据库或文件)的访问权限 为该实例提供一个全局访问节点 在Go中单例模式有两种实现,一种是饿汉式,一种是懒汉式。饿汉式简单,可以将问题及早暴露出来,懒汉式虽然支持延迟加载,但是也将可能的问题延迟到了第一次调用的时候,同时为了实现并发安全,也不得不加锁。 饿汉式 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // Package singleton package singleton // singleton 饿汉式 var singleton *File type File struct{} func init() { singleton = &File{} } func GetInstance() *File { return singleton } 单元测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package tests import ( "testing" "design-pattern/singleton" "github.com/stretchr/testify/assert" ) // TestSingleton 测试单例 func TestInstance(t *testing.T) { assert.Equal(t, singleton.GetInstance(), singleton.GetInstance()) } // BenchmarkSingleton 测试并发访问单例 func BenchmarkInstance(b *testing.B) { b.RunParallel(func(p *testing.PB) { for p.Next() { assert.Equal(b, singleton.GetInstance(), singleton.GetInstance()) } }) } 懒汉式 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // Package lazysingleton 懒汉式单例 package lazysingleton import "sync" var ( lazySingleton *File once sync.Once ) type File struct{} func GetLazyInstance() *File { if lazySingleton == nil { // 使用sync.Once来确保单例在并发时只被初始化一次 once.Do(func() { lazySingleton = &File{} }) } return lazySingleton } 单元测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package tests import ( "testing" "design-pattern/lazysingleton" "github.com/stretchr/testify/assert" ) // TestLazySingleton 测试懒汉式单例 func TestLazyInstance(t *testing.T) { assert.Equal(t, lazysingleton.GetLazyInstance(), lazysingleton.GetLazyInstance()) } // BenchmarkGetLazyInstance 测试懒汉式单例 func BenchmarkGetLazyInstance(b *testing.B) { b.RunParallel(func(p *testing.PB) { for p.Next() { assert.Equal(b, lazysingleton.GetLazyInstance(), lazysingleton.GetLazyInstance()) } }) } 测试结果 1 2 3 4 5 6 7 8 ❯ go test -bench . goos: darwin goarch: arm64 pkg: design-pattern/tests BenchmarkGetLazyInstance-8 3113553 380.8 ns/op BenchmarkInstance-8 3156261 378.5 ns/op PASS ok design-pattern/tests 3.798s 根据bench结果可以发现加锁(sync.Once内部使用的atomic来进行处理)还是会对性能造成影响的,不过也在可接受范围内。 ...
Go设计模式——开闭原则
介绍 简单的说就是:对扩展开放,对修改关闭。对扩展开放是为了应对需求的变化,对修改关闭就是为了保证已有代码的稳定性,最终是为了让系统更具有弹性,能更好的处理需求。 开闭原则也包含了单一职责原则。 我们以消息队列来进行举例。 坏的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 // Package main 开闭原则 Open-Closed Principle // 开闭原则包含了:单一职责原则 package main import "fmt" type KafkaQueue struct{} func (k *KafkaQueue) SendMSG(msg string) error { fmt.Println("Kafka send msg success") return nil } type RabbitQueue struct{} func (r *RabbitQueue) SendMSG(msg string) error { fmt.Println("Rabbitmq send msg success") return nil } type Demo struct{} func (d *Demo) SendByKafka(queue KafkaQueue, msg string) error { return queue.SendMSG(msg) } func (d *Demo) SendByRabbit(queue RabbitQueue, msg string) error { return queue.SendMSG(msg) } func main() { } 通过这个例子,我们可以看出来,这段代码违背了我们的对扩展开放,对修改关闭的原则。当我们需要添加一个新的RocketMQ的时候,需要改动Demo的逻辑以及其他设计的业务逻辑,可扩展性可以说是一点也没有。 ...
Go设计模式——单一职责原则
介绍 类的职责应该是单一的,对外只提供一种功能,而引起类变化的原因应该只有一个。简单的说就是每一个类只负责自己的事情,只有单一的功能。 我们现在以银行工作人员举例: 坏的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // Package main 单一职责原则 Single-Responsibility Principle package main import "fmt" type Banker struct{} // Save 存钱 func (b *Banker) Save(money uint64) error { fmt.Printf("成功存入: %d\n", money) return nil } // Transfer 转账 func (b *Banker) Transfer(money uint64, to string) error { fmt.Printf("成功向: %s转入: %d\n", to, money) return nil } 单一职责原则要求一个类/接口只有一个职责,而引起类变化的原因只能有一个。 从原则上讲,我们为Banker定义存钱和转账的操作是有道理的,因为我们接口中定义的都是银行工作人员可以执行的操作,引起变化的原因只能是Banker的属性和行为发生变化。 从这方便考虑,这种设计是有合理性的,如果能保证需求不会变化或者需求变化的可能行很小,那么这种设计就是合理的。 但是实际上我们知道,需求是不断变化的,今日增加一个股票业务,那么我们就需要增加一个股票的相关属性和行为,我们的接口和实现就需要全部变动。 最好的方式就是当我们开始定义的时候,根据属性和行为进行细分,抽象不同的接口出来,在Go里面也是主张小接口,这样我们可以通过组合的手段来随意构造我们想要的大接口。 好的 我们将Banker进行抽象,这样可以更好的进行扩展: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // Package main 单一职责原则 Single-Responsibility Principle package main import "fmt" type Config struct { Money uint64 To string } type Banker interface { DoSomething(Config) error } type SaveBanker struct{} func (sb *SaveBanker) DoSomething(cfg Config) error { fmt.Printf("成功存入: %d\n", cfg.Money) return nil } type TransferBanker struct{} func (tb *TransferBanker) DoSomething(cfg Config) error { fmt.Printf("成功向: %s转入: %d\n", cfg.To, cfg.Money) return nil } 我们抽象出来了Banker接口,每一个不太的业务员都可以实现这个接口,对行为进行自定义。 ...
如何在Go中使用POSIX命名信号量
go 本身提供的 semaphore 只能在同一个进程多个协程或线程间使用,无法在不同的 go 进程之间使用,所以本文介绍,如何使用 go 中的 syscall 来使用 POSIX 系统提供的命名信号量。 Go 中的系统调用 在 go 中,系统调用是通过 syscall 包提供的 Syscall 函数来进行系统调用的,不同的系统调用有不同的 trap,以及不同长度的参数。 trap go 在 syscall 包中定义了大量的系统调用码,具体定义在文件1.20.6/go/src/syscall/zsysnum_darwin_arm64.go 。不同操作系统上,定义所使用的文件是不同的,这些定义都是通过不同系统的c 语言头文件自动生成的。比如 linux amd64 操作系统的定义在1.20.6/go/src/syscall/zerrors_linux_amd64.go。 不同长度的参数 syscall 包有 Syscall、Syscall6 两个函数,对应于不同的操作系统调用参数长度的情况。 Syscall 总共接收 4 个参数,第一个是 trap 定义,描述具体的系统调用,剩下的 3 个是系统调用所需的参数。 Syscall6 总共接收 7 个参数,第一个是 trap 定义,描述具体的系统调用,剩下的 6 个是系统调用所需的参数。 如果使用 Syscall 或 Syscall6 时,系统调用所需的参数不满足函数形参所需的数量,则剩下的参数传0。 例如,在 POSIX 系统上打开一个命名信号量的系统调用是: 1 sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); 因为系统调用的参数有 4 个,而 Syscall 接收的全部形参才 4 个,所以 Syscall 不能满足我们的需求,只能使用 Syscall6 这个函数。而 Syscall6 总共需要 7 个形参,其中有 6 个是系统调用参数,我们只有 4 个系统调用参数,那么剩下的 2 个系统调用参数,我们就可以使用 0 替代,例如: ...