channel的有趣用法

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 引子 萌叔在阅读tailsamplingprocessor源码时,发现channel的一种有趣的玩法,这里记录一下 FIFO 队列 tailsamplingprocessor中通过NumTraces 设置内存中最多保存的trace的数量, 超过这个阈值,就从按照先进先出的原则,删除最先进入队列的trace // NumTraces is the number of traces kept on memory. Typically most of the data // of a trace is released after a sampling decision is taken. NumTraces uint64 `mapstructure:"num_traces"` processor.go postDeletion := false currTime := time.Now() for !postDeletion { select { case tsp.deleteChan <- id: postDeletion = true default: traceKeyToDrop := <-tsp.deleteChan tsp.dropTrace(traceKeyToDrop, currTime) } } channel刚好满足这个特性, 如果tsp.deleteChan 没有满, 则往tsp.deleteChan写入一个traceID; 如果tsp.deleteChan已经装满,则从队列(tsp.deleteChan)中取出头部元素, 其实就是最先进入队列的traceID,将其对应的trace信息从内存中删除(tsp.dropTrace())。 ...

April 17, 2024 · 1 min

Golang embed(内嵌)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 引言 Golang编译成单个bin文件,发布的时候相当方便了。但是某个配置文件没有办法直接打在bin文件中,比如SSL的密钥,html模板等等。 Golang 1.16 标准库引入了embed包,它可以在编译期间将外部文件嵌入到bin包中。 嵌入的文件都是只读的,因此是协程安全的。 另外由于编译期间嵌入的,因此如果文件发生变化,需要重新编译。 2. 示例 2.2 示例1 嵌入文件夹 传送门 . ├── html │ ├── a.html │ └── index.html └── main.go package main import ( "embed" "log" "net/http" ) //go:embed html var content embed.FS func main() { mutex := http.NewServeMux() mutex.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(content)))) err := http.ListenAndServe(":8080", mutex) if err != nil { log.Fatal(err) } } 2.2 示例2 嵌入模板文件 传送门 . ├── main.go └── template ├── hello.tpl └── hello2.tpl package main import ( "bytes" "embed" "html/template" "log" ) //go:embed template/*.tpl var mytpl embed.FS func main() { check := func(err error) { if err != nil { log.Fatal(err) } } list, err := mytpl.ReadDir("template") check(err) for _, item := range list { log.Println("template/" + item.Name()) } data, err := mytpl.ReadFile("template/hello.tpl") log.Println("data:", string(data)) check(err) t, err := template.New("hello").Parse(string(data)) content := bytes.NewBufferString("") err = t.Execute(content, map[string]string{"name": "张三"}) check(err) log.Println(content.String()) } 2.3 示例3 嵌入字符串/字节数据 传送门 ...

September 2, 2022 · 1 min

Golang常见mock库小结

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | 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 ( "github.com/stretchr/testify/mock" "testing" ) type FakePeople struct { mock.Mock } func (m *FakePeople) Say(str string) string { args := m.Called(str) return args.String(0) } func TestSomething(t *testing.T) { // create an instance of our test object testObj := new(FakePeople) // setup expectations testObj.On("Say", "Jack").Return("hello Jack") testObj.On("Say", "Lucy").Return("hello Lucy") // call the code we are testing expected := "People Say:hello Jack" got := BizLogic(testObj, "Jack") if expected == got { t.Logf("got = %v; expected = %v", got, expected) } else { t.Errorf("got = %v; expected = %v", got, expected) } } 2.3 实现原理 FakePeople是一个实现了接口People的类。有时候它也被称为Stub。它可以被理解为一个占位符,替代对真实的服务交互过程。 ...

June 24, 2021 · 2 min

一个关于go module的有趣话题

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 假如你正在使用go mod管理某个项目的代码库依赖 case1: 某个项目的tag是超过v1版本的,你该怎么定义module case2: 某个项目要同时进行2个版本的开发,v2和v3 你该怎么定义module 2. 问题 为了验证效果,萌叔创建了项目 vearne/mod-multi-version 这个代码库只有一个文件 package mmv import "fmt" const Versoin = "v1.0.0" func PrintVersion(){ fmt.Println("version:", Versoin) } 其它项目使用vearne/mod-multi-version package main import ( "github.com/vearne/mod-multi-version" ) func main() { mmv.PrintVersion() } 代码库的tag列表中包含以下tag v1.0.0 v1.0.2 v2.0.1-Alpha v2.0.5 v2.0.6 v3.0.0 v3.0.1 但如果你试图拉取v2.0.1-Alpha ╰─$ go get github.com/vearne/mod-multi-version@v2.0.1-Alpha go get: github.com/vearne/mod-multi-version@v2.0.1-Alpha: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2 错误提示主版本号只能是v0或者v1 ...

February 25, 2021 · 1 min

利用docker实现Golang程序的交叉编译

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | 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 install vim-common -y WORKDIR /go/src/github.com/vearne/passwordbox/ ADD . /go/src/github.com/vearne/passwordbox/ RUN go get golang:1.14 实际是基于Ubuntu的一个镜像(Linux) 2.2 借助Docker实现跨平台编译 release-linux: docker run -v `pwd`:$(SOURCE_PATH) -t -e GOOS=linux -e GOARCH=amd64 -i $(CONTAINER) go build $(LDFLAGS) -o pwbox tar -zcvf pwbox-$(VERSION)-linux-amd64.tar.gz ./pwbox rm pwbox -v pwd:$(SOURCE_PATH) 是为了把项目的代码直接挂载到docker中,这样就无需每次拷贝代码到docker中 ...

August 3, 2020 · 1 min

GOMAXPROCS你设置对了吗?

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 有圈子的朋友介绍 uber-go/automaxprocs, 我才发现之前在docker中, Golang程序设置的GOMAXPROCS不正确,有必要在重新回顾一下了。 2. Go 调度器: M, P 和 G 我们知道在Go scheduler中,G代表goroutine, P代表Logical Processor, M是操作系统线程。在绝大多数时候,其实P的数量和M的数量是相等。 每创建一个p, 就会创建一个对应的M 只有少数情况下,M的数量会大于P golang runtime是有个sysmon的协程,他会轮询的检测所有的P上下文队列,**只要 G-M 的线程长时间在阻塞状态,那么就重新创建一个线程去从runtime P队列里获取任务。先前的阻塞的线程会被游离出去了,当他完成阻塞操作后会触发相关的callback回调,并加入回线程组里。**简单说,如果你没有特意配置runtime.SetMaxThreads,那么在没有可复用的线程的情况下,会一直创建新线程。 3. GOMAXPROCS的取值 3.1 虚拟机和物理机 我们知道可以通过 runtime.GOMAXPROCS() 来了设定P的值 Go 1.5开始, Go的GOMAXPROCS默认值已经设置为 CPU的核数, 这允许我们的Go程序充分使用机器的每一个CPU,最大程度的提高我们程序的并发性能。 但其实对于IO密集型的场景,我们可以把GOMAXPROCS的值超过CPU核数,在笔者维护的某个服务中,将GOMAXPROCS设为CPU核数的2倍,压测结果表明,吞吐能力大概能提升10% 3.2 容器中 在容器中,Golang程序获取的是宿主机的CPU核数导致GOMAXPROCS设置的过大。比如在笔者的服务中,宿主机是48cores,而实际container只有4cores。 线程过多,会增加上线文切换的负担,白白浪费CPU。 uber-go/automaxprocs 可以解决这个问题 package main import ( "fmt" _ "go.uber.org/automaxprocs" "runtime" ) func main() { // Your application logic here. fmt.Println("real GOMAXPROCS", runtime.GOMAXPROCS(-1)) } 另外也可以通过设置环境变量GOMAXPROCS来改变Golang程序的GOMAXPROCS默认值 ...

September 18, 2019 · 1 min

哈希碰撞攻击与防范机制

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引子 哈希表的原理是用数组来保存键值对,键值对存放的位置(下标)由键的哈希值决定,键的哈希值可以在参数时间内计算出来,这样哈希表插入、查找和删除的时间复杂度为O(1),但是这是理想的情况下,真实的情况是,键的哈希值存在冲突碰撞,也就是不同的键的哈希值可能相等,一个好的哈希函数应该是尽可能的减少碰撞。解决冲突碰撞的方法有分为两种:开放地址法和 链接法,这里不具体展开。哈希表一般都采用链接法来解决冲突碰撞,也就是用一个链表来将分配到同一个桶(键的哈希值一样)的键值对保存起来。 所谓的哈希碰撞攻击就是,针对哈希函数的特性,精心构造数据,使所有数据的哈希值相同,当这些数据保存到哈希表中,哈希表就会退化为单链表,哈希表的各种操作的时间复杂度提升一个数量级,因此会消耗大量CPU资源,导致系统无法快速响应请求,从而达到拒绝服务攻击(Dos)的目的。 绝大多数语言中map的默认实现都是HashMap,比如Golang、Python,Java有2种HashMap和TreeMap 2.攻击方法与防范机制 通过构造哈希值完全相同的字符串 比如 hash(str1) == hash(str2) 找到大量的哈希值相同的str1、str2、str3 … 这种攻击方法依赖的前提是hash函数本身是不变的。 那么如果hash函数是变化的,那么就会大大增加构造哈希值完全相同的字符串的难度。 由此衍生出方案1。 2.1 方案1 加salt 哈希值的计算变为 hash(salt + str) salt为特殊的字符串,一般都是固定值 由于salt是不对外暴露的,所以黑客构造哈希值完全相同的字符串的难度就增大了 2.2 方案2 可变的hash函数 如果hash函数本身就是可变的,那么黑客几乎无法构造哈希值完全相同的字符串。 我们来看Golang是怎么做的 type _type struct { ... kind uint8 alg *typeAlg ... } 每个类型都有其对应的typeAlg // typeAlg is also copied/used in reflect/type.go. // keep them in sync. type typeAlg struct { // function for hashing objects of this type // (ptr to object, seed) -> hash hash func(unsafe.Pointer, uintptr) uintptr // function for comparing objects of this type // (ptr to object A, ptr to object B) -> ==? equal func(unsafe.Pointer, unsafe.Pointer) bool } var algarray = [alg_max]typeAlg{ ... alg_STRING: {strhash, strequal}, alg_INTER: {interhash, interequal}, alg_FLOAT32: {f32hash, f32equal}, alg_FLOAT64: {f64hash, f64equal}, alg_CPLX64: {c64hash, c64equal}, ... } func strhash(a unsafe.Pointer, h uintptr) uintptr { x := (*stringStruct)(a) return memhash(x.str, h, uintptr(x.len)) } 最终我们可以看到 ...

March 7, 2019 · 1 min

如何在Golang中制造stack overflow 故障

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 大家都知道golang的栈的动态增长的,并且是放在堆上的,理论上可以相当的大,那么怎么才能制造一个stack overflow 故障呢? 其实只要人为的加入循环引用就能做到 package main import ( //"github.com/json-iterator/go" "encoding/json" ) type A struct { ElementB *B } type B struct { ElementA *A } func main(){ a := A{} b := B{} a.ElementB = &b b.ElementA = &a //var json = jsoniter.ConfigCompatibleWithStandardLibrary json.Marshal(a) } 栈溢出,程序奔溃,并输出 ╰─$ go run test_stackoverflow2.go runtime: goroutine stack exceeds 1000000000-byte limit fatal error: stack overflow runtime stack: runtime.throw(0x10e2e65, 0xe) /usr/local/Cellar/go/1.11.2/libexec/src/runtime/panic.go:608 +0x72 runtime.newstack() /usr/local/Cellar/go/1.11.2/libexec/src/runtime/stack.go:1008 +0x729 runtime.morestack() /usr/local/Cellar/go/1.11.2/libexec/src/runtime/asm_amd64.s:429 +0x8f goroutine 1 [running]: encoding/json.(*structEncoder).encode(0xc0000642d0, 0xc00007e000, 0x10c8d40, 0xc00000c030, 0x199, 0x100) 看来默认栈的最大大小也就1GB。 这个例子其实源于真实的线上故障,经过测试无论是标准的Json库,或是jsoniter都无法避免此问题。 另外如果Golang编译器,或者Marshal函数能够对递归的深度做出判断,超过一定深度就报错,在栈溢出前,抛出err,避免栈溢出,程序崩溃 后记 已经给json-iterator/go 提了issue,并且提了pull request。 有需要也可以直接使用我个改进库vearne/json-iterator-go 默认递归的最大层级限制是1000 ...

November 30, 2018 · 1 min

简单的GOLANG 协程池2 (带Cancel功能)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 有些朋友可能看我的这篇文章 简单的GOLANG 协程池 这是我做的简单协程池,功能还算完备,但是有些任务执行的时间很长,如果想在协程池运行起来以后就退出,就只能死等。这是非常不友好的,因此我又写了一个新的协程池GContextPool,利用context支持在任务运行过程中,结束任务。原来的GPool仍然可以继续使用。 下面我们来看看用法。 1. 安装 go get github.com/vearne/golib/utils 2. 使用 2.1 创建协程池 // 设定协程池的中协程的数量是30个 cxt, cancel := context.WithTimeout(context.Background(), 1 * time.Second) defer cancel() var p *utils.GContextPool = utils.NewGContextPool(ctx, 30) 2.2 定义任务处理函数 任务处理函数形如 type JobContextFunc func(ctx context.Context, key interface{}) *GPResult 执行任务 GContextPool.ApplyAsync(f JobContextFunc, slice []interface{}) f JobContextFunc 是目标函数 slice []interface{} 任务参数列表 示例 ctx_pool.go package main import ( "context" "fmt" "github.com/vearne/golib/utils" "log" "strconv" "time" ) func JudgeStrWithContext2(ctx context.Context, key interface{}) *utils.GPResult { num, _ := strconv.Atoi(key.(string)) result := &utils.GPResult{} var canceled bool = false for i := 0; i < 60; i++ { select { case <-ctx.Done(): canceled = true result.Value = false result.Err = fmt.Errorf("normal termination") default: time.Sleep(time.Millisecond * 50) } } if !canceled { if num < 450 { result.Value = true } else { result.Value = false } } return result } func main() { cxt, cancel := context.WithTimeout(context.Background(), 1 * time.Second) defer cancel() p := utils.NewGContextPool(cxt,30) slice := make([]interface{}, 0) for i := 0; i < 1000; i++ { slice = append(slice, strconv.Itoa(i)) } result := make([]*utils.GPResult, 0, 10) trueCount := 0 falseCount := 0 start := time.Now() for item := range p.ApplyAsync(JudgeStrWithContext2, slice) { result = append(result, item) if item.Err!= nil{ //log.Println("cancel", item.Err) continue } if item.Value.(bool) { trueCount++ } else { falseCount++ } } log.Printf("cancel, %v, true:%v, false:%v, cost:%v\n", len(result), trueCount, falseCount, time.Since(start)) } 请我喝瓶饮料

November 19, 2018 · 2 min

聊聊go-redis的一些高级用法

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 说到Golang的Redis库,用到最多的恐怕是 redigo 和 go-redis。其中 redigo 不支持对集群的访问。 本文想聊聊go-redis 2个高级用法 2. 开启对Cluster中Slave Node的访问 在一个负载比较高的Redis Cluster中,如果允许对slave节点进行读操作将极大的提高集群的吞吐能力。 开启对Slave 节点的访问,受以下3个参数的影响 type ClusterOptions struct { // Enables read-only commands on slave nodes. ReadOnly bool // Allows routing read-only commands to the closest master or slave node. // It automatically enables ReadOnly. RouteByLatency bool // Allows routing read-only commands to the random master or slave node. // It automatically enables ReadOnly. RouteRandomly bool ... } go-redis 选择节点的逻辑如下 ...

November 18, 2018 · 2 min