深入理解GosyncMap原理剖析

Map is like a Go map[interface{}]interface{} but is safe for concurrent useby multiple goroutines without additional locking or coordination. Loads, stores, and deletes run in amortized constant time. 上面一段是官方对sync.Map 的描述,从描述中看,sync.Map 跟map 很像,sync.Map 的底层实现也是依靠了map,但是sync.Map 相对于 map 来说,是并发安全的。 1. 结构概览1.1. sync.Mapsync.Map的结构体了 type Map struct { mu Mutex // 后面是readOnly结构体,依靠map实现,仅仅只用来读 read atomic.Value // readOnly // 这个map主要用来写的,部分时候也承担读的能力 dirty map[interface{}]*entry // 记录自从上次更新了read之后,从read读取key失败的次数 misses int}1.2. readOnlysync.Map.read属性所对应的结构体了,这里不太明白为什么不把readOnly结构体的属性直接放入到sync.Map结构体里 type readOnly struct { // 读操作所对应的map m map[interface{}]*entry // dirty是否包含m中不存在的key amended bool // true if the dirty map contains some key not in m.}1.3. entryentry就是unsafe.Pointer,记录的是数据存储的真实地址 ...

September 8, 2019 · 4 min · jiezi

Syncthing vs Resilio Sync vs Nextcloud 文件同步服务评测

首先,这是我一直同时在用的 3 个开源同步服务。那为什么我要同时用着多个同步服务呢?主要是因为它们各有优势,有一些无可替代的功能。Resilio Sync 高级版提供的 可选择性同步,让我用 0KB 的占用空间,可以得到所有文件的目录和名称。在我需要的时候,又能以极快的速度下载到本地。这是个我使用极少的功能,但是却是使用中最为爽快的一个功能。分享和下载的时候,我都毫无负担,因为它们都不存在于本地,我只用下载自己想要的文件。Nextcloud 提供的文件 分享 可以让你有更多的选择以及权限控制,只用一条 url 链接,你就可以简单的分享给需要的人,而且还能提供文件操作动态,你可以知道文件在什么时候做了哪些变动,这对你希望监控文件动态的时候非常好用。并且它还提供许多不同类型的 App 拓展,其中包括 Rss程序、Keepass管理程序、音乐播放、视频播放 等诸多功能。Syncthing 这是我 个人文件同步 的主力服务,我用它进行跨设备同步和备份。它是我的 Inbox 文件夹,收集着每台设备上的数据,我用它来进行 数据、库、配置 等文件的同步。它的优点也非常简单,安装简单,网络要求低,提供完善的版本控制。我只需要后台开启,配置好,就无需再担心。当然,我并不单单只依赖上面 3 个同步服务(当然rsync、webdav、ftp我也用,但这不在讨论范围内。),我还搭配电脑的备份服务进行备份。家庭文件服务器还会有快照计划,重要文件也会定时冷备份。这样,我才能任性的对待数据,并再也不用担心它们会消失不见了。: )下面,我们进入正题,对比一下 3 款同步服务的优缺点吧。1. 平台覆盖平台SyncthingResilio SyncNextcloudiOS✘✔︎✔︎Android✔︎✔︎✔︎macOS✔︎✔︎✔︎Windows✔︎✔︎✔︎Linux✔︎✔︎✔︎Linux Arm✔︎✔︎✔︎Docker x86✔︎✔︎✔︎Docker Arm✘✘✔︎Nas System✔︎✔︎✘PS: 主要统计的是官方支持的平台。第三方方案,不计入统计。这里比较遗憾的是 Syncthing 并没有 iOS 客户端,曾经有一款但是现在已经下架了,我手机端主要用 Nextcloud 偶尔用 Resilio Sync(毕竟只同步大文件用)。docker arm 不支持的服务需要自行构建(第三方也可),通常支持 linux arm 的都支持 docker arm,但是官方只构建了 x86 版本。Nextcloud 的 Nas 版本很可能是有的,但是 Nextcloud 官方没介绍,NAS 系统 官方库一般也有下载,毕竟这个服务很普遍了。2. 功能对比功能SyncthingResilio SyncNextcloud版本控制阶段性版本控制回收站限客户端网络环境1. 同步无限制2. 中区中转服务器稀少1. 同步无限制2. 设备发现需国外环境1. 同步无限制2. 应用下载需国外环境3. 部分应用依赖国外服务同步速度1. 内网满带宽2. 外网依赖中转服务器带宽1. 内网满带宽2. 外网依赖同步设备带宽总和1. 内网满带宽2. 外网依赖部署服务器带宽WebDav✘✘✔︎选择同步✘✔︎(高级版)✘文件加密✘✔︎(加密文件夹)✔︎(需设置)同步加密✔︎✔︎✔︎(需启用https)部署难度低低高文件分享✔︎(只能整库分享)✔︎(只能整库分享)✔︎权限管理✔︎✔︎✔︎2.1 网络问题这里重点讨论一下主要影响大家使用的网络问题。2.1.1 Syncthing 的同步速度为什么那么慢?先说结论,原因是由于对 Syncthing 开放且距离你最近的 中继服务器 过少并且速度较慢导致的。PS: https://relays.syncthing.net/ 这里可以看到开放的 中继服务器 列表。(个人使用的中继服务器可以不开放)最开始用的时候,我并没有觉得这个问题影响使用,因为数据量不大(都是配置文件),也就没有在意。自从 Resilio Sync 因为众所周知的问题挂了以后,我把大量同步任务也迁移到了 Syncthing,其中就包括了 虚拟机、多媒体文件、下载的系统文件、备份文件 等大文件数据。但这就要了命了,几十 kb 的速度同步至少按周来算,而且是不关机的那种。这时候,我就想了 2 个办法先缓缓。通过复制/同步到目标机器的方式,把所有文件传输过去。(这种方式不治本,因为虚拟机变化产生的文件很大,如果不经常变动,你可以采用此方法。)修改 文件拉取顺序 为 小文件优先。在文件夹 选项->高级->文件拉取顺序 中修改。——————– (想治本的同学看这里)我是善良友好的分割线 ——————–当然以上方法都是不解决根本问题的。真正解决问题的办法是,自建中继服务器(划重点)。如何构建 Syncthing Relay Server。(官方英文文档)如何设置 Relaying。(官方英文文档)我在测试过的的docker镜像 t4skforce/syncthing-relay。(构建源码可参考)碍于篇幅,这里不能教大家如何去部署。先提供一些资料给大家参考。 : )2.1.2 Resilio Sync 为什么无法找到设备?先说结论,原因是 Resilio Sync 的 trackers and relays 服务器无法访问。解决办法也很简单,让无法访问的地址走代理就可以了。获取配置文件 https://config.resilio.com/sync.conf参考图:碍于篇幅,细节就略略略了。 : )Nextcloud 为什么无法访问应用页面下载应用?先说结论,原因是因为应用商店无法访问。你可以自行去 GitHub 下载 App 项目。并解压到 NEXTCLOUD-PATH/apps 目录下,按照项目教程进行部署。解决部署服务器无法访问 Nextcloud App Servier 无法访问的问题。碍于篇幅,略略略。 : )2.2 版本控制既然和数据有关,那最害怕的是什么?那当然就是数据 同步异常、数据丢失、数据误删、意外导致数据丢失 等数据消失不见的严重问题了。——————– (结论看这里)我是善良友好的分割线 ——————–这里不讲如何保障数据,直接说结论:以上软件所提供的版本控制,都无法完全保证数据同步过程中不丢失。所以不要认为有了版本控制,数据就可以随意处理了。有时候你想找回某个数据还真不一定找得到。(自行搭配快照、副本、备份。)——————– (评测看这里)我是善良友好的分割线 ——————–Syncthing 提供的 版本控制 非常多,可以适应多种场景下使用。其中 阶段版本控制 提供了 小时 级别的历史记录,最大程度的保障数据安全,并且提供了历史记录查看器,可以很方便的查看历史记录,并恢复。基本上,它可以适应所有个人同步需求,并且同步过程中对数据也相对安全。参考图:Resilio Sync 并不提供版本控制功能,只有最简单的回收站机制。甚至你也不清楚有没有放入回收站。所以它只适合 分享型、大文件型、变动少、文件相对而言不那么重要、目录层次少结构不复杂 等使用环境。我基本上,都是用来放大文件和多媒体文件。Nextcloud 提供文件变化版本控制,但仅限于使用其客户端的方式。通过 WebDav 等访问的方式,是 没有版本控制 的,由于其使用数据库来记录所有文件,所以文件数量和结构,考验着你的数据库服务器。并且其 http 传输原理导致默认对 文件大小 有所限制。当然以上问题,都能通过其他方法来解决,但是我仍然 不推荐用来作为主要的同步服务。但是其丰富的拓展性,以及详尽的文件记录,非常适合 分享 和 多人协作,适合对外提供服务,可以弥补 Syncthing 这类个人同步服务的短板,也就是协作和分享。3. 结语这 3 款开源同步服务,在同步速度上,都是可以满速运行的,同步速度上体验没多大区别。但是由于各自服务的机制不同,需要一定动手能力,才能达到最佳效果。以上只是对这 3 款开源服务的一些细节做了一些对比。如果大家有比较关心的其他细节,再做补充。Bye. : ) ...

March 28, 2019 · 1 min · jiezi

【Go】优雅的读取http请求或响应的数据

原文链接:https://blog.thinkeridea.com/…从 http.Request.Body 或 http.Response.Body 中读取数据方法或许很多,标准库中大多数使用 ioutil.ReadAll 方法一次读取所有数据,如果是 json 格式的数据还可以使用 json.NewDecoder 从 io.Reader 创建一个解析器,假使使用 pprof 来分析程序总是会发现 bytes.makeSlice 分配了大量内存,且总是排行第一,今天就这个问题来说一下如何高效优雅的读取 http 中的数据。背景介绍我们有许多 api 服务,全部采用 json 数据格式,请求体就是整个 json 字符串,当一个请求到服务端会经过一些业务处理,然后再请求后面更多的服务,所有的服务之间都用 http 协议来通信(啊, 为啥不用 RPC,因为所有的服务都会对第三方开放,http + json 更好对接),大多数请求数据大小在 1K4K,响应的数据在 1K8K,早期所有的服务都使用 ioutil.ReadAll 来读取数据,随着流量增加使用 pprof 来分析发现 bytes.makeSlice 总是排在第一,并且占用了整个程序 1/10 的内存分配,我决定针对这个问题进行优化,下面是整个优化过程的记录。pprof 分析这里使用 https://github.com/thinkeridea/go-extend/blob/master/exnet/exhttp/expprof/pprof.go 中的 API 来实现生产环境的 /debug/pprof 监测接口,没有使用标准库的 net/http/pprof 包因为会自动注册路由,且长期开放 API,这个包可以设定 API 是否开放,并在规定时间后自动关闭接口,避免存在工具嗅探。服务部署上线稳定后(大约过了一天半),通过 curl 下载 allocs 数据,然后使用下面的命令查看分析。$ go tool pprof allocsFile: xxxType: alloc_spaceTime: Jan 25, 2019 at 3:02pm (CST)Entering interactive mode (type “help” for commands, “o” for options)(pprof) topShowing nodes accounting for 604.62GB, 44.50% of 1358.61GB totalDropped 776 nodes (cum <= 6.79GB)Showing top 10 nodes out of 155 flat flat% sum% cum cum% 111.40GB 8.20% 8.20% 111.40GB 8.20% bytes.makeSlice 107.72GB 7.93% 16.13% 107.72GB 7.93% github.com/sirupsen/logrus.(*Entry).WithFields 65.94GB 4.85% 20.98% 65.94GB 4.85% strings.Replace 54.10GB 3.98% 24.96% 56.03GB 4.12% github.com/json-iterator/go.(*frozenConfig).Marshal 47.54GB 3.50% 28.46% 47.54GB 3.50% net/url.unescape 47.11GB 3.47% 31.93% 48.16GB 3.55% github.com/json-iterator/go.(*Iterator).readStringSlowPath 46.63GB 3.43% 35.36% 103.04GB 7.58% handlers.(*AdserviceHandler).returnAd 42.43GB 3.12% 38.49% 84.62GB 6.23% models.LogItemsToBytes 42.22GB 3.11% 41.59% 42.22GB 3.11% strings.Join 39.52GB 2.91% 44.50% 87.06GB 6.41% net/url.parseQuery从结果中可以看出采集期间一共分配了 1358.61GB top 10 占用了 44.50% 其中 bytes.makeSlice 占了接近 1/10,那么看看都是谁在调用 bytes.makeSlice 吧。(pprof) web bytes.makeSlice从上图可以看出调用 bytes.makeSlice 的最终方法是 ioutil.ReadAll, (受篇幅影响就没有截取 ioutil.ReadAll 上面的方法了),而 90% 都是 ioutil.ReadAll 读取 http 数据调用,找到地方先别急想优化方案,先看看为啥 ioutil.ReadAll 会导致这么多内存分配。func readAll(r io.Reader, capacity int64) (b []byte, err error) { var buf bytes.Buffer // If the buffer overflows, we will get bytes.ErrTooLarge. // Return that as an error. Any other panic remains. defer func() { e := recover() if e == nil { return } if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge { err = panicErr } else { panic(e) } }() if int64(int(capacity)) == capacity { buf.Grow(int(capacity)) } , err = buf.ReadFrom(r) return buf.Bytes(), err}func ReadAll(r io.Reader) ([]byte, error) { return readAll(r, bytes.MinRead)}以上是标准库 ioutil.ReadAll 的代码,每次会创建一个 var buf bytes.Buffer 并且初始化 buf.Grow(int(capacity)) 的大小为 bytes.MinRead, 这个值呢就是 512,按这个 buffer 的大小读取一次数据需要分配 2~16 次内存,天啊简直不能忍,我自己创建一个 buffer 好不好。看一下火焰图????吧,其中红框标记的就是 ioutil.ReadAll 的部分,颜色比较鲜艳。优化读取方法自己创建足够大的 buffer 减少因为容量不够导致的多次扩容问题。buffer := bytes.NewBuffer(make([]byte, 4096)), err := io.Copy(buffer, request.Body)if err !=nil{ return nil, err}恩恩这样应该差不多了,为啥是初始化 4096 的大小,这是个均值,即使比 4096 大基本也就多分配一次内存即可,而且大多数数据都是比 4096 小的。但是这样真的就算好了吗,当然不能这样,这个 buffer 个每请求都要创建一次,是不是应该考虑一下复用呢,使用 sync.Pool 建立一个缓冲池效果就更好了。以下是优化读取请求的简化代码:package adapterimport ( “bytes” “io” “net/http” “sync” “github.com/json-iterator/go” “github.com/sirupsen/logrus” “github.com/thinkeridea/go-extend/exbytes”)type Adapter struct { pool sync.Pool}func New() *Adapter { return &Adapter{ pool: sync.Pool{ New: func() interface{} { return bytes.NewBuffer(make([]byte, 4096)) }, }, }}func (api *Adapter) GetRequest(r *http.Request) (*Request, error) { buffer := api.pool.Get().(*bytes.Buffer) buffer.Reset() defer func() { if buffer != nil { api.pool.Put(buffer) buffer = nil } }() _, err := io.Copy(buffer, r.Body) if err != nil { return nil, err } request := &Request{} if err = jsoniter.Unmarshal(buffer.Bytes(), request); err != nil { logrus.WithFields(logrus.Fields{ “json”: exbytes.ToString(buffer.Bytes()), }).Errorf(“jsoniter.UnmarshalJSON fail. error:%v”, err) return nil, err } api.pool.Put(buffer) buffer = nil // …. return request, nil}使用 sync.Pool 的方式是不是有点怪,主要是 defer 和 api.pool.Put(buffer);buffer = nil 这里解释一下,为了提高 buufer 的复用率会在不使用时尽快把 buffer 放回到缓冲池中,defer 之所以会判断 buffer != nil 主要是在业务逻辑出现错误时,但是 buffer 还没有放回缓冲池时把 buffer 放回到缓冲池,因为在每个错误处理之后都写 api.pool.Put(buffer) 不是一个好的方法,而且容易忘记,但是如果在确定不再使用时 api.pool.Put(buffer);buffer = nil 就可以尽早把 buffer 放回到缓冲池中,提高复用率,减少新建 buffer。这样就好了吗,别急,之前说服务里面还会构建请求,看看构建请求如何优化吧。package adapterimport ( “bytes” “fmt” “io” “io/ioutil” “net/http” “sync” “github.com/json-iterator/go” “github.com/sirupsen/logrus” “github.com/thinkeridea/go-extend/exbytes”)type Adapter struct { pool sync.Pool}func New() *Adapter { return &Adapter{ pool: sync.Pool{ New: func() interface{} { return bytes.NewBuffer(make([]byte, 4096)) }, }, }}func (api *Adapter) Request(r *Request) (*Response, error) { var err error buffer := api.pool.Get().(*bytes.Buffer) buffer.Reset() defer func() { if buffer != nil { api.pool.Put(buffer) buffer = nil } }() e := jsoniter.NewEncoder(buffer) err = e.Encode(r) if err != nil { logrus.WithFields(logrus.Fields{ “request”: r, }).Errorf(“jsoniter.Marshal failure: %v”, err) return nil, fmt.Errorf(“jsoniter.Marshal failure: %v”, err) } data := buffer.Bytes() req, err := http.NewRequest(“POST”, “http://xxx.com”, buffer) if err != nil { logrus.WithFields(logrus.Fields{ “data”: exbytes.ToString(data), }).Errorf(“http.NewRequest failed: %v”, err) return nil, fmt.Errorf(“http.NewRequest failed: %v”, err) } req.Header.Set(“User-Agent”, “xxx”) httpResponse, err := http.DefaultClient.Do(req) if httpResponse != nil { defer func() { io.Copy(ioutil.Discard, httpResponse.Body) httpResponse.Body.Close() }() } if err != nil { logrus.WithFields(logrus.Fields{ “url”: “http://xxx.com”, }).Errorf(“query service failed %v”, err) return nil, fmt.Errorf(“query service failed %v”, err) } if httpResponse.StatusCode != 200 { logrus.WithFields(logrus.Fields{ “url”: “http://xxx.com”, “status”: httpResponse.Status, “status_code”: httpResponse.StatusCode, }).Errorf(“invalid http status code”) return nil, fmt.Errorf(“invalid http status code”) } buffer.Reset() , err = io.Copy(buffer, httpResponse.Body) if err != nil { return nil, fmt.Errorf(“adapter io.copy failure error:%v”, err) } respData := buffer.Bytes() logrus.WithFields(logrus.Fields{ “response_json”: exbytes.ToString(respData), }).Debug(“response json”) res := &Response{} err = jsoniter.Unmarshal(respData, res) if err != nil { logrus.WithFields(logrus.Fields{ “data”: exbytes.ToString(respData), “url”: “http://xxx.com”, }).Errorf(“adapter jsoniter.Unmarshal failed, error:%v”, err) return nil, fmt.Errorf(“adapter jsoniter.Unmarshal failed, error:%v”, err) } api.pool.Put(buffer) buffer = nil // … return res, nil}这个示例和之前差不多,只是不仅用来读取 http.Response.Body 还用来创建一个 jsoniter.NewEncoder 用来把请求压缩成 json 字符串,并且作为 http.NewRequest 的 body 参数, 如果直接用 jsoniter.Marshal 同样会创建很多次内存,jsoniter 也使用 buffer 做为缓冲区,并且默认大小为 512, 代码如下:func (cfg Config) Froze() API { api := &frozenConfig{ sortMapKeys: cfg.SortMapKeys, indentionStep: cfg.IndentionStep, objectFieldMustBeSimpleString: cfg.ObjectFieldMustBeSimpleString, onlyTaggedField: cfg.OnlyTaggedField, disallowUnknownFields: cfg.DisallowUnknownFields, } api.streamPool = &sync.Pool{ New: func() interface{} { return NewStream(api, nil, 512) }, } // ….. return api}而且序列化之后会进行一次数据拷贝:func (cfg *frozenConfig) Marshal(v interface{}) ([]byte, error) { stream := cfg.BorrowStream(nil) defer cfg.ReturnStream(stream) stream.WriteVal(v) if stream.Error != nil { return nil, stream.Error } result := stream.Buffer() copied := make([]byte, len(result)) copy(copied, result) return copied, nil}既然要用 buffer 那就一起吧^^,这样可以减少多次内存分配,下读取 http.Response.Body 之前一定要记得 buffer.Reset(), 这样基本就已经完成了 http.Request.Body 和 http.Response.Body 的数据读取优化了,具体效果等上线跑一段时间稳定之后来查看吧。效果分析上线跑了一天,来看看效果吧$ go tool pprof allocs2File: connect_serverType: alloc_spaceTime: Jan 26, 2019 at 10:27am (CST)Entering interactive mode (type “help” for commands, “o” for options)(pprof) topShowing nodes accounting for 295.40GB, 40.62% of 727.32GB totalDropped 738 nodes (cum <= 3.64GB)Showing top 10 nodes out of 174 flat flat% sum% cum cum% 73.52GB 10.11% 10.11% 73.52GB 10.11% git.tvblack.com/tvblack/connect_server/vendor/github.com/sirupsen/logrus.(*Entry).WithFields 31.70GB 4.36% 14.47% 31.70GB 4.36% net/url.unescape 27.49GB 3.78% 18.25% 54.87GB 7.54% git.tvblack.com/tvblack/connect_server/models.LogItemsToBytes 27.41GB 3.77% 22.01% 27.41GB 3.77% strings.Join 25.04GB 3.44% 25.46% 25.04GB 3.44% bufio.NewWriterSize 24.81GB 3.41% 28.87% 24.81GB 3.41% bufio.NewReaderSize 23.91GB 3.29% 32.15% 23.91GB 3.29% regexp.(*bitState).reset 23.06GB 3.17% 35.32% 23.06GB 3.17% math/big.nat.make 19.90GB 2.74% 38.06% 20.35GB 2.80% git.tvblack.com/tvblack/connect_server/vendor/github.com/json-iterator/go.(*Iterator).readStringSlowPath 18.58GB 2.56% 40.62% 19.12GB 2.63% net/textproto.(*Reader).ReadMIMEHeader哇塞 bytes.makeSlice 终于从前十中消失了,真的太棒了,还是看看 bytes.makeSlice 的其它调用情况吧。(pprof) web bytes.makeSlice从图中可以发现 bytes.makeSlice 的分配已经很小了, 且大多数是 http.Request.ParseForm 读取 http.Request.Body 使用 ioutil.ReadAll 原因,这次优化的效果非常的好。看一下更直观的火焰图????吧,和优化前对比一下很明显 ioutil.ReadAll 看不到了优化期间遇到的问题比较惭愧在优化的过程出现了一个过失,导致生产环境2分钟故障,通过自动部署立即回滚才得以快速恢复,之后分析代码解决之后上线才完美优化,下面总结一下出现的问题吧。在构建 http 请求时我分了两个部分优化,序列化 json 和读取 http.Response.Body 数据,保持一个观点就是尽早把 buffer 放回到缓冲池,因为 http.DefaultClient.Do(req) 是网络请求会相对耗时,在这个之前我把 buffer 放回到缓冲池中,之后读取 http.Response.Body 时在重新获取一个 buffer,大概代码如下:package adapterimport ( “bytes” “fmt” “io” “io/ioutil” “net/http” “sync” “github.com/json-iterator/go” “github.com/sirupsen/logrus” “github.com/thinkeridea/go-extend/exbytes”)type Adapter struct { pool sync.Pool}func New() *Adapter { return &Adapter{ pool: sync.Pool{ New: func() interface{} { return bytes.NewBuffer(make([]byte, 4096)) }, }, }}func (api *Adapter) Request(r *Request) (*Response, error) { var err error buffer := api.pool.Get().(*bytes.Buffer) buffer.Reset() defer func() { if buffer != nil { api.pool.Put(buffer) buffer = nil } }() e := jsoniter.NewEncoder(buffer) err = e.Encode(r) if err != nil { return nil, fmt.Errorf(“jsoniter.Marshal failure: %v”, err) } data := buffer.Bytes() req, err := http.NewRequest(“POST”, “http://xxx.com”, buffer) if err != nil { return nil, fmt.Errorf(“http.NewRequest failed: %v”, err) } req.Header.Set(“User-Agent”, “xxx”) api.pool.Put(buffer) buffer = nil httpResponse, err := http.DefaultClient.Do(req) // …. buffer = api.pool.Get().(*bytes.Buffer) buffer.Reset() defer func() { if buffer != nil { api.pool.Put(buffer) buffer = nil } }() _, err = io.Copy(buffer, httpResponse.Body) if err != nil { return nil, fmt.Errorf(“adapter io.copy failure error:%v”, err) } // …. api.pool.Put(buffer) buffer = nil // … return res, nil}上线之后马上发生了错误 http: ContentLength=2090 with Body length 0 发送请求的时候从 buffer 读取数据发现数据不见了或者数据不够了,我去这是什么鬼,马上回滚恢复业务,然后分析 http.DefaultClient.Do(req) 和 http.NewRequest,在调用 http.NewRequest 是并没有从 buffer 读取数据,而只是创建了一个 req.GetBody 之后在 http.DefaultClient.Do 是才读取数据,因为在 http.DefaultClient.Do 之前把 buffer 放回到缓冲池中,其它 goroutine 获取到 buffer 并进行 Reset 就发生了数据争用,当然会导致数据读取不完整了,真实汗颜,对 http.Client 了解太少,争取有空撸一遍源码。总结使用合适大小的 buffer 来减少内存分配,sync.Pool 可以帮助复用 buffer, 一定要自己写这些逻辑,避免使用三方包,三方包即使使用同样的技巧为了避免数据争用,在返回数据时候必然会拷贝一个新的数据返回,就像 jsoniter 虽然使用了 sync.Pool 和 buffer 但是返回数据时还需要拷贝,另外这种通用包并不能给一个非常贴合业务的初始 buffer 大小,过小会导致数据发生拷贝,过大会太过浪费内存。程序中善用 buffer 和 sync.Pool 可以大大的改善程序的性能,并且这两个组合在一起使用非常的简单,并不会使代码变的复杂。转载:本文作者: 戚银(thinkeridea)本文链接: https://blog.thinkeridea.com/201901/go/you_ya_de_du_qu_http_qing_qiu_huo_xiang_ying_de_shu_ju.html版权声明: 本博客所有文章除特别声明外,均采用 CC BY 4.0 CN协议 许可协议。转载请注明出处! ...

January 26, 2019 · 6 min · jiezi