Golang 内存分配优化

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 如果你正在使用imroc/req io.Copy 或 ioutil.ReadAll,或者尝试对大量对象的内存分配和释放的场景进行优化,这篇文章可能对你有帮助 2. 起因 在我们的一个程序中,使用库imroc/req请求后端的HTTP服务, 从HTTP响应读取结果 有如下调用关系 Resq.Bytes() -> Resq.ToBytes() -> ioutil.ReadAll(r io.Reader) -> Buff.ReadFrom(r io.Reader) 使用pprof收集内存累计分配情况可以发现大量的内存分配由Buffer.grow(n int) 触发 3. 原因分析 ReadFrom的源码如下 // MinRead is the minimum slice size passed to a Read call by // Buffer.ReadFrom. As long as the Buffer has at least MinRead bytes beyond // what is required to hold the contents of r, ReadFrom will not grow the // underlying buffer. const MinRead = 512 // ReadFrom reads data from r until EOF and appends it to the buffer, growing // the buffer as needed. The return value n is the number of bytes read. Any // error except io.EOF encountered during the read is also returned. If the // buffer becomes too large, ReadFrom will panic with ErrTooLarge. func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) { b.lastRead = opInvalid for { i := b.grow(MinRead) m, e := r.Read(b.buf[i:cap(b.buf)]) if m < 0 { panic(errNegativeRead) } b.buf = b.buf[:i+m] n += int64(m) if e == io.EOF { return n, nil // e is EOF, so return nil explicitly } if e != nil { return n, e } } } 由于ioutil.ReadAll不知道最终需要多大的空间来存储结果数据,它采取的做法是,初始分配一个较小的Buff(大小为MinRead), 一边从输入中读取数据放入Buff, 一边看是否能够存下所有数据,如果不能则尝试扩大这个Buff(调用Buffer.grow()) ...

October 8, 2018 · 3 min

gin 优雅退出

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 gin底层使用的是net/http, 所以gin的优雅退出就等于 http.Server的优雅退出, Golang 1.8以后提供了Shutdown函数,可以优雅关闭http.Server func (srv *Server) Shutdown(ctx context.Context) error 优雅退出的过程 1)关闭所有的监听 2)后关闭所有的空闲连接 3)无限期等待活动的连接处理完毕转为空闲,并关闭 如果提供了带有超时的Context,将在服务关闭前返回 Context的超时错误 完整的例子 package main import ( "net/http" "time" "os" "os/signal" "syscall" "fmt" "github.com/gin-gonic/gin" "github.com/pkg/errors" ) func SlowHandler(c *gin.Context) { fmt.Println("[start] SlowHandler") //time.Sleep(30 * time.Second) time.Sleep(30 * time.Second) fmt.Println("[end] SlowHandler") c.JSON(http.StatusOK, gin.H{ "message": "success", }) } // 实现context.Context接口 type ExitContext struct{ Chan chan struct{} DeadLineTime time.Time } func NewExitContext(duration time.Duration) *ExitContext{ cxt := ExitContext{} cxt.DeadLineTime = time.Now().Add(duration) cxt.Chan = make(chan struct{}, 1) return &cxt } func (cxt *ExitContext) Done() <-chan struct{}{ if time.Now().After(cxt.DeadLineTime){ cxt.Chan <- struct{}{} } return cxt.Chan } func (cxt *ExitContext) Err() error{ return errors.New("can't exit before Specified time") } // 无意义的空函数 func(cxt *ExitContext) Value(key interface{}) interface{}{ return nil } func(ctx *ExitContext) Deadline() (deadline time.Time, ok bool){ deadline = ctx.DeadLineTime ok = true return } func main() { r := gin.Default() // 1. r.GET("/slow", SlowHandler) server := &http.Server{ Addr: ":8080", Handler: r, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } go server.ListenAndServe() // 设置优雅退出 gracefulExitWeb(server) } func gracefulExitWeb(server *http.Server) { ch := make(chan os.Signal) signal.Notify(ch, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT) sig := <-ch fmt.Println("got a signal", sig) now := time.Now() cxt := NewExitContext(3*time.Second) err := server.Shutdown(cxt) if err != nil{ fmt.Println("err", err) } // 看看实际退出所耗费的时间 fmt.Println("------exited--------", time.Since(now)) } 更简单的写法 ...

September 14, 2018 · 2 min

玩转高性能日志库ZAP (2)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 uber开源的高性能日志库zap, 除了性能远超logrus之外,还有很多诱人的功能,比如支持日志采样、支持通过HTTP服务动态调整日志级别。不过他原生不支持文件归档,如果要支持文件按大小或者时间归档,必须要使用第三方库, 根据官方资料参考资料1,官方推荐的是 natefinch/lumberjack 示例 package main import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) // logpath 日志文件路径 // loglevel 日志级别 func initLogger(logpath string, loglevel string) *zap.Logger { hook := lumberjack.Logger{ Filename: logpath, // 日志文件路径 MaxSize: 1024, // megabytes MaxBackups: 3, // 最多保留3个备份 MaxAge: 7, //days Compress: true, // 是否压缩 disabled by default } w := zapcore.AddSync(&hook) var level zapcore.Level switch loglevel { case "debug": level = zap.DebugLevel case "info": level = zap.InfoLevel case "error": level = zap.ErrorLevel default: level = zap.InfoLevel } encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder core := zapcore.NewCore( zapcore.NewConsoleEncoder(encoderConfig), w, level, ) logger := zap.New(core) logger.Info("DefaultLogger init success") return logger } func main() { logger := initLogger("/tmp/all.log", "info") logger.Info("test log", zap.Int("line", 47)) } 文件的清理策略如下 Cleaning Up Old Log Files Whenever a new logfile gets created, old log files may be deleted. The most recent files according to the encoded timestamp will be retained, up to a number equal to MaxBackups (or all of them if MaxBackups is 0). Any files with an encoded timestamp older than MaxAge days are deleted, regardless of MaxBackups. Note that the time encoded in the timestamp is the rotation time, which may differ from the last time that file was written to. ...

September 12, 2018 · 2 min

玩转高性能日志库zap (1)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 uber开源的高性能日志库zap, 除了性能远超logrus之外,还有很多诱人的功能,比如支持数据采样、支持通过HTTP服务动态调整日志级别 参考资料1虽然提到了可以通过HTTP服务动态调整日志级别,但是没有给出可用的代码实现,这里给出一个样例。 动态调整日志级别 非常简单直接上代码 package main import ( "fmt" "go.uber.org/zap" "net/http" "time" ) func main() { alevel := zap.NewAtomicLevel() http.HandleFunc("/handle/level", alevel.ServeHTTP) go func() { if err := http.ListenAndServe(":9090", nil); err != nil { panic(err) } }() // 默认是Info级别 logcfg := zap.NewProductionConfig() logcfg.Level = alevel logger, err := logcfg.Build() if err != nil { fmt.Println("err", err) } defer logger.Sync() for i := 0; i < 1000; i++ { time.Sleep(1 * time.Second) logger.Debug("debug log", zap.String("level", alevel.String())) logger.Info("Info log", zap.String("level", alevel.String())) } } 查看日志级别 ...

September 12, 2018 · 1 min

简单的Golang 协程池

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 引言 很多时候,还是需要用到协程池的,简单撸了一个,方便工作中的需要,有bug请反馈 1. 安装 go get github.com/vearne/golib/utils 2. 使用 2.1 创建协程池 // 协程池的大小是30 var p *utils.GPool = utils.NewGPool(30) 2.2 定义任务处理函数 任务处理函数形如 type JobFunc func(param interface{}) *GPResult GPResult type GPResult struct { Value interface{} Err error } 执行任务 ApplyAsync(f JobFunc, slice []interface{}) 示例 pool.go package main import ( "github.com/vearne/golib/utils" "log" "strconv" ) func Judge(key interface{}) *utils.GPResult { result := &utils.GPResult{} num, _ := strconv.Atoi(key.(string)) if num < 450 { result.Value = true } else { result.Value = false } return result } func main() { p := utils.NewGPool(30) slice := make([]interface{}, 0) for i := 0; i < 1000; i++ { slice = append(slice, strconv.Itoa(i)) } result := make([]bool, 0, 10) trueCount := 0 falseCount := 0 for item := range p.ApplyAsync(Judge, slice) { value := item.Value.(bool) result = append(result, value) if value { trueCount++ } else { falseCount++ } } log.Printf("cancel, %v, true:%v, false:%v\n", len(result), trueCount, falseCount) } 致谢 程序的API形式以及思路参考了python的multiprocessing模块,再此表示感谢 ...

September 5, 2018 · 1 min

基于redis的分布式限频库

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 总述 这个库的目标是为了能够简单粗暴的实现分布式限频功能, 类似于ID生成器的用法, client每次从Redis拿回-批数据(在这里只是一个数值)进行消费, 只要没有消费完,就没有达到频率限制。 English README 优势 依赖少,只依赖redis,不需要专门的服务 使用的redis自己的时钟,不需要相应的服务器时钟完全一致 线程(协程)安全 系统开销小,对redis的压力很小 安装 go get github.com/vearne/ratelimit 用法 1. 创建 redis.Client 依赖 “github.com/go-redis/redis” client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "xxx", // password set DB: 0, // use default DB }) 2. 创建限频器 limiter, _ := ratelimit.NewRedisRateLimiter(client, "push", 1 * time.Second, 200, 10, ratelimit.TokenBucketAlg, ) 表示允许每秒操作200次 limiter, _ := ratelimit.NewRedisRateLimiter(client, "push", 1 * time.Minute, 200, 10, ratelimit.TokenBucketAlg, ) 表示允许每分钟操作200次 ...

August 24, 2018 · 2 min

Golang strings中的Index函数(字符串查找)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 在Golang中strings模块Index原型如下 func Index(s, substr string) int Golang中 substr长度大于64/32(视CPU的情况而定)的情况, 查找采用的rabin-karp算法,它是由Richard M. Karp和 Michael O. Rabin在1987年提出的。(1987年就这么牛了,还让人活不活) 该算法的实际应用是检测抄袭 2. 点评 假定s长度为n, substr长度为m 它的时间复杂度为O(n + m),最坏情况下的时间复杂度为O(n * m)。 KMP算法的时间复杂度也是O(n + m), 可见它的速度并不慢 它的相比于KMP算法的最大优势是,实现简单,易于理解 2.1 以下是暴力算法的伪码 function NaiveSearch(string s[1..n], string pattern[1..m]) for i from 1 to n-m+1 for j from 1 to m if s[i+j-1] ≠ pattern[j] jump to next iteration of outer loop return i return not found 2.2 以下是rabin-karp算法的伪码 function RabinKarp(string s[1..n], string pattern[1..m]) hpattern := hash(pattern[1..m]); for i from 1 to n-m+1 hs := hash(s[i..i+m-1]) if hs = hpattern if s[i..i+m-1] = pattern[1..m] return i return not found 大家可以看出rabin-karp算法其实是暴力算法的改进版, 简单的说: 就是先匹配Pattern的哈希值,如果hash值相同,才实际进行字符串匹配(line 4/5/6) ...

July 30, 2018 · 2 min

聊聊Gossip的一个实现

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 Gossip是一种去中心化、容错并保证最终一致性的协议。它的基本思想是通过不断的和集群中的节点gossip交换信息,经过 O(log(n))个回合, gossip协议即可将信息传递到所有的节点。 这里介绍Gossip的一个实现库hashicorp/memberlist,并讲一下需要注意的事项。 2. hashicorp/memberlist memberlist是HashiCorp公司开源的Gossip库,这个库被consul(也是HashiCorp公司开源的)所引用。 它是SWIM的一个扩展实现。 下面的例子test_gossip.go中它被用来做集群节点发现 package main import ( "flag" "fmt" "github.com/hashicorp/memberlist" // "net" "os" "strconv" "time" ) var ( bindPort = flag.Int("port", 8001, "gossip port") ) func main() { flag.Parse() hostname, _ := os.Hostname() config := memberlist.DefaultLocalConfig() config.Name = hostname + "-" + strconv.Itoa(*bindPort) // config := memberlist.DefaultLocalConfig() config.BindPort = *bindPort config.AdvertisePort = *bindPort fmt.Println("config.DisableTcpPings", config.DisableTcpPings) fmt.Println("config.IndirectChecks", config.IndirectChecks) fmt.Println("config.RetransmitMult", config.RetransmitMult) fmt.Println("config.PushPullInterval", config.PushPullInterval) fmt.Println("config.ProbeInterval", config.ProbeInterval) fmt.Println("config.GossipInterval", config.GossipInterval) fmt.Println("config.GossipNodes", config.GossipNodes) fmt.Println("config.BindPort", config.BindPort) list, err := memberlist.Create(config) if err != nil { panic("Failed to create memberlist: " + err.Error()) } // Join an existing cluster by specifying at least one known member. // 配置种子节点, 这里我直接写死了 _, err = list.Join([]string{"127.0.0.1:8001", "127.0.0.1:8002"}) fmt.Println("err", err) if err != nil { panic("Failed to join cluster: " + err.Error()) } // Ask for members of the cluster for { fmt.Println("-------------start--------------") for _, member := range list.Members() { fmt.Printf("Member: %s %s\n", member.Name, member.Addr) } fmt.Println("-------------end--------------") time.Sleep(time.Second * 3) } } 可以直接在单机上进行测试,启动 ...

July 4, 2018 · 3 min

算法题 leecode 525.连续数组

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 题目 给定一个二进制数组, 找到含有相同数量的 0 和 1 的最长连续子数组。 实例1: 输入: [0,1] 输出: 2 说明: [0, 1] 是具有相同数量0和1的最长连续子数组。 实例2 输入: [0,1,0] 输出: 2 说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。 解题思路 注意 数组中只会有0、1 两种数据 相同数量的 0 和 1 的最长连续子数组中肯定包含有相同数量的0和1 这里我们要利用0和1数量相同,互相抵消的特性 如果我们把0都当做-1, 1还是1,沿着数组求和,对于以下数组 [0, 1, 1, 0, 1, 1, 1, 0] 我们可以想到如果2个位置(i, j)之间有相同数量0和1的最长连续数组, 那么这2个位置的sum(i) == sum(j) 这里要注意的是,对于数组初始的sum值是0,我们可以记为sum(-1) = 0 要找到最长连续子数组,只需找到sum值相同,且位置相距最大的情况即可 最终代码 find_max.go func findMaxLength(nums []int) int { // sum -> index // 存最小值 cache := map[int]int{} cache[0] = -1 sum := 0 res := 0 for i := 0; i < len(nums); i++ { if nums[i] == 0 { sum -= 1 } else { sum += 1 } if _, ok := cache[sum]; ok { index := cache[sum] res = max(i-index, res) } else { cache[sum] = i } } return res } func max(a int, b int) int { if a < b { return b } else { return a } } 请我喝瓶饮料

June 13, 2018 · 1 min

算法题 leecode 851. 喧闹和富有

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc leecode 上第851题 1. 题目如下 在一组 N 个人(编号为 0, 1, 2, …, N-1)中,每个人都有不同数目的钱,以及不同程度的安静(quietness)。 为了方便起见,我们将编号为 x 的人简称为 “person x “。 如果能够肯定 person x 比 person y 更有钱的话,我们会说 richer[i] = [x, y] 。注意 richer 可能只是有效观察的一个子集。 另外,如果 person x 的安静程度为 q ,我们会说 quiet[x] = q 。 现在,返回答案 answer ,其中 answer[x] = y 的前提是,在所有拥有的钱不少于 person x 的人中,person y 是最安静的人(也就是安静值 quiet[y] 最小的人)。 示例: 输入:richer = [[1,0],[2,1],[3,1],[3,7],[4,3],[5,3],[6,3]], quiet = [3,2,5,4,6,1,7,0] 输出:[5,5,2,5,4,5,6,7] 解释: answer[0] = 5, person 5 比 person 3 有更多的钱,person 3 比 person 1 有更多的钱,person 1 比 person 0 有更多的钱。 唯一较为安静(有较低的安静值 quiet[x])的人是 person 7, 但是目前还不清楚他是否比 person 0 更有钱。 answer[7] = 7, 在所有拥有的钱肯定不少于 person 7 的人中(这可能包括 person 3,4,5,6 以及 7), 最安静(有较低安静值 quiet[x])的人是 person 7。 其他的答案也可以用类似的推理来解释。 2. 提示 2.1 思考 对于示例,我们能够根据richer信息得出如下关系 我们能够得到富有程度的关系如上图 ...

June 12, 2018 · 2 min