Golang标准库的读写文件,没有开启用户空间文件缓冲区?

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 大家都知道写文件时数据流转的顺序是 用户空间文件缓冲区 -> 内核空间文件缓冲区 -> 内核空间IO队列 默认的ANSI C库,对用户空间文件缓冲区有三种方式 全缓冲 行缓冲 无缓冲 难道Golang没有?笔者不敢断定,但有以下的对比试验或许能说明些问题 write.go package main import ( "os" "log" "time" ) func main() { file, err := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) if err != nil { log.Fatal(err) } for i := 0; i < 1000; i++ { time.Sleep(2 * time.Second) file.Write([]byte("xxxx")) } file.Close() } write2.go write2.go 使用了标准库提供的bufio, 给写文件增加了4096byte的缓冲区 package main import ( "os" "log" "time" "bufio" ) func main(){ file, err := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) writer := bufio.NewWriterSize(file, 4096) if err!= nil{ log.Fatal(err) } for i:=0;i<1000;i++{ time.Sleep(2 * time.Second) writer.Write([]byte("xxxx")) } writer.Flush() file.Close() } 使用strace跟踪系统调用, 在没有使用bufio的情况如下 ...

November 2, 2018 · 2 min

聊聊Golang中的锁(2)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 在 聊聊golang中的锁(1) 中,笔者提到Golang的锁,可能引发的非常高的CPU消耗,本文我们一起来探究一下,CPU时钟都消耗再了哪里。 2. 分析 修改代码, 使用pprof package main import ( "fmt" "sync" "time" _ "net/http/pprof" "net/http" "log" ) type LockBox struct{ sync.Mutex Value int } func deal(wpg *sync.WaitGroup, bp *LockBox, count int){ for i:=0;i< count;i++{ bp.Lock() bp.Value++ bp.Unlock() } wpg.Done() } func main() { go func() { log.Println(http.ListenAndServe(":18081", nil)) }() timeStart := time.Now() workerCount := 100 taskCount := 10000000000 var wg sync.WaitGroup var box LockBox for i:=0;i<workerCount;i++{ wg.Add(1) go deal(&wg, &box, taskCount/workerCount) } wg.Wait() fmt.Println("cost", time.Since(timeStart)) } 记录采样运行数据 ...

October 10, 2018 · 1 min

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

聊聊Golang中的锁(1)--可能造成较高的CPU消耗

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 关于Golang中的锁的实现和源码早就文章阐述过了,这里我从实验的角度让大家看看对协程数量对任务执行速度,以及CPU开销的影响 运行环境 CPU: Intel(R) Xeon(R) CPU E5-2650 v4 @ 2.20GHz 8核 内存:16G GO version 1.10 协程数量分别是1, 2, 5, 10, 100, 200下完成1000000000次计数所花 总时间 CPU消耗 CPU峰值 分析 观察图可以发现, 协程数量是1的时候运行总时间反而最短,只耗时2.8秒,协程数量10时,运行总时间最长,耗时21.6秒 协程数量是100时,CPU峰值最大,为530%,协程数量是1是,CPU峰值最小,不到100% 代码 package main import ( "fmt" "sync" "time" ) type LockBox struct{ sync.Mutex Value int } func deal(wpg *sync.WaitGroup, bp *LockBox, count int){ for i:=0;i< count;i++{ bp.Lock() bp.Value++ bp.Unlock() } wpg.Done() } func main() { timeStart := time.Now() //workerCount := 1 //workerCount := 2 workerCount := 200 taskCount := 1000000000 var wg sync.WaitGroup var box LockBox for i:=0;i<workerCount;i++{ wg.Add(1) go deal(&wg, &box, taskCount/workerCount) } wg.Wait() fmt.Println("cost", time.Since(timeStart)) } 请我喝瓶饮料

October 8, 2018 · 1 min

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

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 引言 前几天有朋友问我,zap库是否支持同时打印到多个目标地址,比如1份打印到文件,1份到控制台,1份打印到kafka中。这是所有日志库都会支持的功能,zap当然也不例外。 示例 package main import ( "io/ioutil" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack" "os" ) func main(){ // First, define our level-handling logic. // 仅打印Error级别以上的日志 highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.ErrorLevel }) // 打印所有级别的日志 lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.DebugLevel }) hook := lumberjack.Logger{ Filename: "/tmp/abc.log", MaxSize: 1024, // megabytes MaxBackups: 3, MaxAge: 7, //days Compress: true, // disabled by default } topicErrors := zapcore.AddSync(ioutil.Discard) fileWriter := zapcore.AddSync(&hook) // High-priority output should also go to standard error, and low-priority // output should also go to standard out. consoleDebugging := zapcore.Lock(os.Stdout) // Optimize the Kafka output for machine consumption and the console output // for human operators. kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) // Join the outputs, encoders, and level-handling functions into // zapcore.Cores, then tee the four cores together. core := zapcore.NewTee( // 打印在kafka topic中(伪造的case) zapcore.NewCore(kafkaEncoder, topicErrors, highPriority), // 打印在控制台 zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority), // 打印在文件中 zapcore.NewCore(consoleEncoder, fileWriter, highPriority), ) // From a zapcore.Core, it's easy to construct a Logger. logger := zap.New(core) defer logger.Sync() logger.Info("constructed a info logger", zap.Int("test", 1)) logger.Error("constructed a error logger", zap.Int("test", 2)) } 输出结果 文件中 ...

September 17, 2018 · 2 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 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