关于buffer:nodeBuffer

Buffer(缓冲器)Buffer对象示意固定长度的字节序列,Buffer是Unit8Array的子类。 Buffer的构造和数组很像,操作的办法也和数组相似数组中不能存储二进制的文件,而buffer就是专门用来存储二进制数据应用buffer不须要引入模块,间接应用即可在buffer中存储的都是二进制数据,然而在显示时都是以16进制的模式显示(二进制显示的太长了,不不便显示)buffer中每一个元素的范畴是从00 - ff (即:0-255) (即:00000000-11111111),buffer中的一个元素占用一个字节(8bit)。buffer.length输入的是占用内存的大小。创立Buffer1. 利用字符串创立buffer:Buffer.fromlet str = "RTSP"let buf = Buffer.from(str)console.log(buf) // <Buffer 52 54 53 50>2. 应用Buffer办法创立: Buffer.alloclet buf = new Buffer(10)// ORlet buf = Buffer.alloc(10)3. Buffer.allocUnsafe(size)Buffer.allocUnsafe和Buffer.alloc的最大区别是应用allocUnsafe函数申请到的内存空间是没有被初始化的,也就是说可能还残留了上次应用的数据,因而会有数据安全的问题。allocUnsafe函数接管一个size参数作为buffer区域的大小: const buff = Buffer.allocUnsafe(5)console.log(buff)// Prints (理论内容可能有出入): <Buffer 8b 3f 01 00 00>创立buffer的时候实际上是为buffer分配内存间断的空间,如果在运行的时候再增加空间就不肯定是间断空间了,这样buffer就不好保护了。转换为字符串buf.tostring() // 将缓冲区中的数据转换为字符串

April 20, 2023 · 1 min · jiezi

关于buffer:故障分析-MySQL唯一键约束失效

作者:胡呈清 爱可生 DBA 团队成员,善于故障剖析、性能优化,集体博客:https://www.jianshu.com/u/a95...,欢送探讨。 本文起源:原创投稿 *爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 最近遇到一个故障:单主模式5节点 MGR 集群,在应用 mysqlshell 导入数据时,所有secondary节点报错惟一键抵触退出集群。最终失去的论断是:mysqlshell importTable() 导入数据时会做 SET unique_checks=0 操作,在满足肯定条件时会导致惟一键束缚生效,主库插入了反复的数据,但从库会插入失败从而报错。上面咱们就来聊聊这是怎么回事。 unique_checks=0 的原理(会导致惟一键束缚生效吗?)看看官网文档是怎么形容的: If you have UNIQUE constraints on secondary keys, you can speed up table imports by temporarily turning off the uniqueness checks during the import session:SET unique_checks=0;... SQL import statements ...SET unique_checks=1;For big tables, this saves a lot of disk I/O because InnoDB can use its change buffer to write secondary index records in a batch. Be certain that the data contains no duplicate keys.对大数据量的导入,无论是 sql 文件还是 csv 文件,如果表中有惟一索引,咱们能够通过 SET unique_checks=1; 来放慢导入速度。这里用到的是 change buffer 的原理: ...

February 14, 2022 · 3 min · jiezi

关于buffer:WebGPU-中的缓冲映射机制

1. 什么是缓冲映射就不给定义了,间接简略的说,映射(Mapping)后的某块显存,就能被 CPU 拜访。 三大图形 API(D3D12、Vulkan、Metal)的 Buffer(指显存)映射后,CPU 就能拜访它了,此时留神,GPU 依然能够拜访这块显存。这就会导致一个问题:IO抵触,这就须要程序考量这个问题了。 WebGPU 禁止了这个行为,改用传递“所有权”来示意映射后的状态,颇具 Rust 的哲学。每一个时刻,CPU 和 GPU 是单边拜访显存的,也就防止了竞争和抵触。 当 JavaScript 申请映射显存时,所有权并不是马上就能移交给 CPU 的,GPU 这个时候可能手头上还有别的解决显存的操作。所以,GPUBuffer 的映射办法是一个异步办法: const someBuffer = device.createBuffer({ /* ... */ })await someBuffer.mapAsync(GPUMapMode.READ, 0, 4) // 从 0 开始,只映射 4 个字节// 之后就能够应用 getMappedRange 办法获取其对应的 ArrayBuffer 进行缓冲操作不过,解映射操作倒是一个同步操作,CPU 用完后就能够解映射: somebuffer.unmap()留神,mapAsync 办法将会间接在 WebGPU 外部往设施的默认队列中压入一个操作,此办法作用于 WebGPU 中三大时间轴中的 队列时间轴。而且在 mapAsync 胜利后,内存才会减少(实测)。 当向队列提交指令缓冲后(此指令缓冲的某个渲染通道要用到这块 GPUBuffer),内存上的数据才会提交给 GPU(猜想)。 因为测试地不多,我在调用 destroy 办法后并未显著看到内存的变少,心愿有敌人能测试。 创立时映射能够在创立缓冲时传递 mappedAtCreation: true,这样甚至都不须要申明其 usage 带有 GPUBufferUsage.MAP_WRITE const buffer = device.createBuffer({ usage: GPUBufferUsage.UNIFORM, size: 256, mappedAtCreation: true,})// 而后马上就能够获取映射后的 ArrayBufferconst mappedArrayBuffer = buffer.getMappedRange()/* 在这里执行一些写入操作 */// 解映射,还管理权给 GPUbuffer.unmap()2 缓冲数据的流向2.1 CPU 至 GPUJavaScript 这端会在 rAF 中频繁地将大量数据传递给 GPUBuffer 映射进去的 ArrayBuffer,而后随着解映射、提交指令缓冲到队列,最初传递给 GPU. ...

February 12, 2022 · 1 min · jiezi

关于buffer:深入浅出FlatBuffers原理

简介: FlatBuffers 是一个开源的、跨平台的、高效的、提供了多种语言接口的序列化工具库。实现了与 Protocal Buffers 相似的序列化格局。次要由 Wouter van Oortmerssen 编写,并由 Google 开源。本文将基于高德地图数据编译增量公布应用了FlatBuffers序列化工具,为大家分享FlatBuffers的原理。 作者 | 大向起源 | 阿里技术公众号 一 前言FlatBuffers 是一个开源的、跨平台的、高效的、提供了多种语言接口的序列化工具库。实现了与 Protocal Buffers 相似的序列化格局。次要由 Wouter van Oortmerssen 编写,并由 Google 开源。Oortmerssen 最后为 Android 游戏和重视性能的利用而开发了 FlatBuffers,当初它具备 C ++、C#、C、Go、Java、PHP、Python 和 JavaScript 的接口。 高德地图数据编译增量公布应用了FlatBuffers序列化工具,借此契机对FlatBuffers原理进行钻研并分享于此。本文简略介绍 FlatBuffers Scheme,通过分析 FlatBuffers 序列化与反序列化原理,重点答复以下问题: 问题1:FlatBuffers 如何做到反序列化速度极快的(或者说无需解码)。问题2:FlatBuffers 如何做到默认值不占存储空间的(Table 构造内的变量)。问题3:FlatBuffers 如何做到字节对齐的。问题4:FlatBuffers 如何做到向前向后兼容的(Struct 构造除外)。问题5:FlatBuffers 在 add 字段时有没有程序要求(Table 构造)。问题6:FlatBuffers 如何依据 Scheme 主动生成编解码器。问题7:FlatBuffers 如何依据 Scheme 主动生成 Json。二 FlatBuffers SchemeFlatBuffers 通过 Scheme 文件定义数据结构,Schema 定义与其余框架应用的IDL(Interface description language)语言相似简略易懂,FlatBuffers 的 Scheme 是一品种 C 的语言(只管 FlatBuffers 有本人的接口定义语言 Scheme 来定义要与之序列化的数据,但它也反对 Protocol Buffers 中的 .proto 格局)。上面以官网 Tutorial 中的 monster.fbs 为例进行阐明: ...

July 20, 2021 · 4 min · jiezi

从入门到放弃Java并发编程NIOBuffer

前言上篇【从入门到放弃-Java】并发编程-NIO-Channel中我们学习到channel是双向通道,数据通过channel在实体(文件、socket)和缓冲区(buffer)中可以双向传输。 本文我们就来学习下buffer 简介buffer即缓冲区,实际上是一块内存,可以用来写入、读取数据。是一个线性的、大小有限的、顺序承载基础数据类型的内存块。 buffer有三个重要的属性: capacity:缓冲池大小,是不可变的。当buffer写满时,需要先清空才能继续写入。limit:是buffer中不可以被读或者写的第一个元素的位置,limit的大小永远不会超过capacity(在写模式下,limit等于capacity)position:是buffer中可以被读或者写的第一个元素的位置,position的大小永远不会超过limit除了boolean外,每一个基础数据类型都有对应的buffer。如:ByteBuffer、CharBuffer、LongBuffer等 buffer不是线程安全的,如果要在多线程中使用 需要加锁控制 接下来以ByteBuffer为例开始学习。 ByteBufferallocateDirectpublic static ByteBuffer allocateDirect(int capacity) { //会创建一个容量大小为capacity的DirectByteBuffer(ByteBuffer的子类) return new DirectByteBuffer(capacity);}allocatepublic static ByteBuffer allocate(int capacity) { if (capacity < 0) throw createCapacityException(capacity); //会创建一个容量大小为capacity的HeapByteBuffer(ByteBuffer的子类) return new HeapByteBuffer(capacity, capacity);}HeapByteBuffer和DirectByteBuffer的区别: DirectByteBuffer是直接调用native方法在本机os::malloc()创建堆外内存;HeapByteBuffer是直接在jvm的堆中分配内存。当buffer中的数据和磁盘、网络等的交互都在操作系统的内核中发生时,使用DirectByteBuffer能避免从内核态->用户态->内核态的切换开销,所有的处理都在内核中进行,性能会比较好当频繁创建操作数据量比较小的buffer时,使用HeapByteBuffer在jvm堆中分配内存能抵消掉使用DirectByteBuffer带来的好处。wrappublic static ByteBuffer wrap(byte[] array, int offset, int length){ try { return new HeapByteBuffer(array, offset, length); } catch (IllegalArgumentException x) { throw new IndexOutOfBoundsException(); }}public static ByteBuffer wrap(byte[] array) { return wrap(array, 0, array.length); }将byte数组包装成一个ByteBuffer ...

July 8, 2019 · 2 min · jiezi

go-bufio-缓冲读写详解级实例

go在提供了io包的同时也提供了bufio包来实现有缓存的读写操作以提高读写性能。为什么bufio性能比io高呢? 缓冲读写缓冲读// 默认缓冲区大小const ( defaultBufSize = 4096)// 最小缓冲区大小 自定义小于次阈值将会被覆盖const minReadBufferSize = 16// 使用默认缓冲区大小bufio.NewReader(rd io.Reader)// 使用自定义缓冲区大小bufio.NewReaderSize(rd io.Reader, size int)缓冲读的大致过程如下,设定好缓冲区大小buf_size后,读取的字节数为rn,缓冲的字节数为bn: 如果缓冲区为空,且 rn >= buf_size,则直接从文件读取,不启用缓冲。如果缓冲区为空,且 rn < buf_size,则从文件读取buf_size 字节的内容到缓冲区,程序再从缓冲区中读取rn字节的内容,此时缓冲区剩余bn = buf_size - rn字节。如果缓冲区不为空,rn < bn,则从缓冲区读取rn字节的内容,不发生文件IO。如果缓冲区不为空,rn >= bn,则从缓冲区读取bn字节的内容,不发生文件IO,缓冲区置为空,回归1/2步骤。缓冲读通过预读,可以在一定程度上减少文件IO次数,故提高性能。 代码演示: package mainimport ( "bufio" "fmt" "strings")func main() { // 用 strings.Reader 模拟一个文件IO对象 strReader := strings.NewReader("12345678901234567890123456789012345678901234567890") // go 的缓冲区最小为 16 byte,我们用最小值比较容易演示 bufReader := bufio.NewReaderSize(strReader, 16) // bn = 0 但 rn >= buf_size 缓冲区不启用 发生文件IO tmpStr := make([]byte, 16) n, _ := bufReader.Read(tmpStr) // bufReader buffered: 0, content: 1234567890123456 fmt.Printf("bufReader buffered: %d, content: %s\n", bufReader.Buffered(), tmpStr[:n]) // bn = 0 rn < buf_size 缓冲区启用 // 缓冲区从文件读取 buf_size 字节 发生文件IO // 程序从缓冲区读取 rn 字节 // 缓冲区剩余 bn = buf_size - rn 字节 tmpStr = make([]byte, 15) n, _ = bufReader.Read(tmpStr) // bufReader buffered: 1, content: 789012345678901 fmt.Printf("bufReader buffered: %d, content: %s\n", bufReader.Buffered(), tmpStr[:n]) // bn = 1 rn > bn // 程序从缓冲区读取 bn 字节 缓冲区置空 不发生文件IO // 注意这里只能读到一个字节 tmpStr = make([]byte, 10) n, _ = bufReader.Read(tmpStr) // bufReader buffered: 0, content: 2 fmt.Printf("bufReader buffered: %d, content: %s\n", bufReader.Buffered(), tmpStr[:n]) // bn = 0 rn < buf_size 启用缓冲读 发生文件IO // 缓冲区从文件读取 buf_size 字节 // 程序从缓冲区读取 rn 字节 // 缓冲区剩余 bn = buf_size - rn 字节 tmpStr = make([]byte, 10) n, _ = bufReader.Read(tmpStr) // bufReader buffered: 6, content: 3456789012 fmt.Printf("bufReader buffered: %d, content: %s\n", bufReader.Buffered(), tmpStr[:n]) // bn = 6 rn <= bn // 则程序冲缓冲区读取 rn 字节 不发生文件IO tmpStr = make([]byte, 3) n, _ = bufReader.Read(tmpStr) // bufReader buffered: 3, content: 345 fmt.Printf("bufReader buffered: %d, content: %s\n", bufReader.Buffered(), tmpStr[:n]) // bn = 3 rn <= bn // 则程序冲缓冲区读取 rn 字节 不发生文件IO tmpStr = make([]byte, 3) n, _ = bufReader.Read(tmpStr) // bufReader buffered: 0, content: 678 fmt.Printf("bufReader buffered: %d, content: %s\n", bufReader.Buffered(), tmpStr[:n])}要注意的是当缓冲区中有内容时,程序的此次读取都会从缓冲区读,而不会发生文件IO。只有当缓冲区为空时,才会发生文件IO。如果缓冲区的大小足够,则启用缓冲读,先将内容载入填满缓冲区,程序再从缓冲区中读取。如果缓冲区过小,则会直接从文件读取,而不使用缓冲读。 ...

June 15, 2019 · 3 min · jiezi

Go语言xtermjswebsocket-Web终端堡垒机

1.前言因为公司业务需要在自己的私有云服务器上添加添加WebSsh终端,同时提供输入命令审计功能. 从google上可以了解到xterm.js是一个非常出色的web终端库,包括VSCode很多成熟的产品都使用这个前端库.使用起来也比较简单. 难点是怎么把ssh命令行转换成websocket通讯,来提供Stdin,stdout输出到xterm.js中,接下来就详解技术细节. 全部代码都可以在我的Github.com/dejavuzhou/felix中可以查阅到. 2.知识储备linux下载stdin,stdou和stderr简单概念熟悉Golang官方库golang.org/x/crypto/ssh了解gorilla/websocket的基本用法gin-gonic/gin,当然你也可以使用其他的路由包替代,或者直接使用标准库(前端)websocket(前端)xterm.js3.数据逻辑图Golang堡垒机主要功能就是把SSH协议数据使用websocket协议转发给xterm.js浏览器. 堡垒机Golang服务UML 4.代码实现4.1创建gin Handler func注册gin路由 api.GET("ws/:id", internal.WsSsh) ssh2ws/internal/ws_ssh.go package internalimport ( "bytes" "github.com/dejavuzhou/felix/flx" "github.com/dejavuzhou/felix/models" "github.com/dejavuzhou/felix/utils" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "github.com/sirupsen/logrus" "net/http" "strconv" "time")var upGrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024 * 1024 * 10, CheckOrigin: func(r *http.Request) bool { return true },}// handle webSocket connection.// first,we establish a ssh connection to ssh server when a webSocket comes;// then we deliver ssh data via ssh connection between browser and ssh server.// That is, read webSocket data from browser (e.g. 'ls' command) and send data to ssh server via ssh connection;// the other hand, read returned ssh data from ssh server and write back to browser via webSocket API.func WsSsh(c *gin.Context) { v, ok := c.Get("user") if !ok { logrus.Error("jwt token can't find auth user") return } userM, ok := v.(*models.User) if !ok { logrus.Error("context user is not a models.User type obj") return } cols, err := strconv.Atoi(c.DefaultQuery("cols", "120")) if wshandleError(c, err) { return } rows, err := strconv.Atoi(c.DefaultQuery("rows", "32")) if wshandleError(c, err) { return } idx, err := parseParamID(c) if wshandleError(c, err) { return } mc, err := models.MachineFind(idx) if wshandleError(c, err) { return } client, err := flx.NewSshClient(mc) if wshandleError(c, err) { return } defer client.Close() startTime := time.Now() ssConn, err := utils.NewSshConn(cols, rows, client) if wshandleError(c, err) { return } defer ssConn.Close() // after configure, the WebSocket is ok. wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) if wshandleError(c, err) { return } defer wsConn.Close() quitChan := make(chan bool, 3) var logBuff = new(bytes.Buffer) // most messages are ssh output, not webSocket input go ssConn.ReceiveWsMsg(wsConn, logBuff, quitChan) go ssConn.SendComboOutput(wsConn, quitChan) go ssConn.SessionWait(quitChan) <-quitChan //write logs xtermLog := models.TermLog{ EndTime: time.Now(), StartTime: startTime, UserId: userM.ID, Log: logBuff.String(), MachineId: idx, MachineName: mc.Name, MachineIp: mc.Ip, MachineHost: mc.Host, UserName: userM.Username, } err = xtermLog.Create() if wshandleError(c, err) { return } logrus.Info("websocket finished")}代码详解 ...

May 27, 2019 · 6 min · jiezi

MySQL-InnoDB特性-Buffer-Pool漫谈

缓存管理是DBMS的核心系统,用于管理数据页的访问、刷脏和驱逐;虽然操作系统本身有page cache,但那不是专门为数据库设计的,所以大多数数据库系统都是自己来管理缓存。由于几乎所有的数据页访问都涉及到Buffer Pool,因此buffer pool的并发访问控制尤为重要,可能会影响到吞吐量和响应时间,本文主要回顾一下MySQL的buffer Pool最近几个版本的发展(若有遗漏,欢迎评论补充), 感受下最近几年这一块的进步 MySQL5.5之前只能设置一个buffer pool, 通过innodb_buffer_pool_size来控制, 刷脏由master线程承担,扩展性差。 MySQL 5.5引入参数innodb_buffer_pool_instances,将buffer pool拆分成多个instance,从而减少对buffer pool的访问控制,这时候的刷脏还是由Master线程来承担。 MySQL 5.6引入了buffer Pool page Id转储和导入特性,也就是说可以随时把内存中的page no存下来到文件里,在重启时会自动把这些Page加载到内存中,使内存保持warm状态. 此外该版本第一次引入了page cleaner,将flush list/lru上的刷脏驱逐工作转移到单独线程,减少了master线程的负担 MySQL 5.7这个版本发布了一个重要特性:online buffer pool resize. 当然是否是online需要打一个问号,因为在resize的过程中需要拿很多全局大锁,在高负载场景下很容易导致实例Hang住(81615)。 和之前不同,buffer pool被分成多个instance,每个instance又由多个chunk组成,每个chunk的大小受到参数innodb_buffer_pool_chunk_size控制,默认128MB, buffer pool resize都是以chunk为单位增加或减少的。另外一个需要注意的点是:你配置的Buffer Pool Size可能比你实际使用的内存要大,尤其对于大Bp而言,这是因为内部做了对齐处理, buffer pool size必须以 innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances来做向上对齐(80350) 我们知道通常数据文件的IO都被设置成O_DIRECT, 但每次修改后依然需要去做fsync,来持久化元数据信息,而对于某些文件系统而言是没必要做fsync的,因此加入了新选项O_DIRECT_NO_FSYNC,这个需求来自于facebook. 他们也对此做了特殊处理:除非文件size变化,否则不做fsync。(最近在buglist上对这个参数是否安全的讨论也很有意思,官方文档做了新的说明,感兴趣的可以看看 [94912:O_DIRECT_NO_FSYNC possible write hole](https://bugs.mysql.com/bug.php?id=94912))) 再一个重要功能是终于引入了multiple page cleaner, 可以多个后台线程并发刷脏页,提供了更好的刷脏性能,有效避免用户线程进入single page flush。当然这还不够完美,主要有四点: 用户线程依然会进入single page flush,而一旦大量线程进入,就会导致严重性能下降:超频繁的fsync,激烈的dblwr竞争,线程切换等等当redo空间不足时,用户线程也会进入page flush,这在高负载场景下是很常见的,你会发现系统运行一段时间后,性能急剧下降。这是因为redo产生太快,而page flush又跟不上,导致checkpoint无法推进。那么用户线程可能就要过来做fuzzy checkpoint了。那时候性能基本上没法看了。dblwr成为重要的单点瓶颈。 如果你的服务器不支持原子写的话,必须打开double write buffer。写入Ibdata一段固定区域,这里是有锁包含的,区分为两部分:single page flush和batch flush, 但无论如何,即使拆分了多个page cleaner,最终扩展性还是受限于dblwr没有专用的lru evict线程,都是Page cleaner键值的。举个简单的例子,当buffer pool占满,同时又有很多脏页时,Page cleaner可能忙于刷脏,而用户线程则得不到free page,从而陷入single page flush如果你对上述几个问题极不满意,可以尝试percona server, 他们向来擅长优化Io bound场景的性能,并且上述几个问题都解决了,尤其是dblwr,他们做了多分区的改进。 ...

April 29, 2019 · 1 min · jiezi

细说双Buffer缓冲池

前言缓冲机制是对数据持久化的延迟,减少不必要的IO,提高数据落盘的效率。本文将会详细探讨拥有双Buffer的缓冲池(下文统称TwinsBufferPool)是如何实现的,读者可以依此推广,得到N-Buffer的实现原理。在此篇文章中,缓冲区(Buffer)和缓冲池(BufferPool)是两个重要的概念,很明显,两者构成了一个包含与被包含的关系,一个缓冲池内可以有一个或者多个缓冲区协同工作,缓冲池中的所有缓冲区被组织成了一个环形队列,一前一后的两个缓冲区可以互相替换角色。当然,在整个过程中,还会有其他辅助工具的出现,在下文都会逐一阐述。一、设计要点1、可扩展性。毫无疑问,可扩展性是对一个设计良好的软件的一项基本要求,而一个软件的可扩展的地方通常是有很多处的,这在某种程度上会依赖于编程者的经验,如果仅仅局限于产品需求,可能会严重限制了软件的可扩展性。缓冲池是一种相对通用的中间件,扩展点相对比较多,比如:缓冲区数量可指定,线程安全与否,缓冲区阈值调配等等。2、易用性。设计出来的中间件应该是对用户友好的,使用过程中不会有繁琐的配置,奇形怪状的API,更不能有诸多不必要的Dependencies,如果能做到代码无侵入性,那就非常完美了。基于这个要求,TwinsBufferPool做成了一个Spring Boot Starter的形式,加入到项目里的dependencies中即可开启使用。3、稳定性。这就是衡量一个中间件好坏的重要KPI之一,从外观上看,同样是一艘船,破了一个洞和完好无缺将会是一个致命的区别,用户期望自己搭上了一艘完整的船,以便能航行万里而无忧。4、高效性。说到稳定性,那就不得不说高效了,如果能帮助用户又好又快的解决问题,无疑是最完美的结果。关于TwinsBufferPool的稳定性和高效性两个指标,会在文中附上jemeter的压测结果,并加以说明。二、设计方案这一小节将会给出TwinsBufferPool完整的设计方案,我们先从配置说起。每个参数都会提供默认值,所以不做任何配置也是允许的。如下是目前TwinsBufferPool能提供的配置参数(yml):buffer: capacity: 2000 threshold: 0.5 allow-duplicate: true pool: enable-temporary-storage: true buffer-time-in-seconds: 120下面附上参数说明表:以上参数比较浅显易懂,这里重点解释enable-temporary-storage和buffer-time-in-seconds这两个参数。根据参数说明,很明显可以感受到,这两个参数是为了预防突发情况,导致数据丢失。因为缓冲区都是基于内存的设计的,这就意味着缓冲的数据随时处于一种服务重启,或者服务宕机的高风险环境中,因此,才会有这两个参数的诞生。因为TwinsBufferPool良好的接口设计,对于以上两个参数的实现机制也是高度可扩展的。TwinsBufferPool默认的是基于Redis的实现,用户也可以用MongoDB,MySQL,FileSystem等方式实现。由此又会衍生出另外一个问题,由于各种异常情况,导致临时存储层遗留了一定量的数据,需要在下次启动的时候,恢复这一部分的数据。总而言之,数据都是通过flush动作最终持久化到磁盘上。因为大多数实际业务场景对于缓冲池的并发量是有一定要求的,所以默认就采用了线程安全的实现策略,受到JDK中ThreadPool的启发,缓冲池也具备了自身状态管理的机制。如下列出了缓冲池所有可能存在的状态,以及各个状态的流转。/** * 缓冲池暂未就绪 /private static final int ST_NOT_READY = 1;/* * 缓冲池初始化完毕,处于启动状态 /private static final int ST_STARTED = 2;/* * 如果安全关闭缓冲池,会立即进入此状态 /private static final int ST_SHUTTING_DOWN = 3;/* * 缓冲池已关闭 /private static final int ST_SHUTDOWN = 4;/* * 正在进行数据恢复 */private static final int ST_RECOVERING = 5;通过上述的一番分析,设计的方案也呼之欲出了,下面给出主要的接口设计与实现。通过以上的讲解,也不难理解BufferPool定义的接口。缓冲池的整个生命周期,以及内部的一些运作机制都得以体现。值得注意的是,在设计上,将缓冲池和存储层做了逻辑分离,使得扩展性进一步得到增强。存储相关的接口包含了一些简单的CURD,目前默认是用Redis作为临时存储层,MongoDB作为永久存储层,用户可以根据需要实现其他的存储方式。下图展现的是TwinsBufferPool的实现方式,DataBuffer是缓冲区,必须依赖的基础元素。因为设计的是环形队列,所以依赖了CycleQueue,这个环形队列的interface也是自定义的,在JDK中没有找到比较合适的实现。值得注意的是,BufferPool接口定义是灵活可扩展的,TwinsBufferPool只是提供了一种基于环形队列的实现方式,用户也可以自行设计,使用另外一种数据结构来支撑缓冲池的运作。三、压测报告使用的是个人的PC电脑,机器的配置如下:处理器:i5-7400 CPU 3.00GHZ 四核内存:8.00GB操作系统:Windows10 64位 基于x64的处理器运行环境如下:jdk 1.8.0_144SpringBoot_2.1.0,内置Tomcat9.0Redis_v4.0.1MongoDB_v3.4.7测试工具:jemeter_v5.1总共测试了四组参数,每组参数主要是针对最大容量,阈值和最大缓冲时间三个参数来做调整。第一组:buffer: capacity: 1000 threshold: 0.8 pool: buffer-time-in-seconds: 60第二组:buffer: capacity: 5000 threshold: 0.8 pool: buffer-time-in-seconds: 60第三组buffer: capacity: 5000 threshold: 0.8 pool: buffer-time-in-seconds: 300第四组buffer: capacity: 10000 threshold: 0.8 pool: buffer-time-in-seconds: 300总共采集了9个指标:CPU占用率,堆内存/M,线程数,错误率,吞吐量/sec,最长响应时间/ms,最短响应时间/ms,平均响应时间/ms,数据丢失量。限于篇幅,只展示4个指标:堆内存,数据丢失量,平均响应时间,吞吐量。总体来看,随着每秒并发量的增加,各项指标呈现了不太乐观的趋势,其中最不稳定的是第四组参数,波动较为明显,综合表现最佳的是第二组参数,其次是第三组。数据丢失量是一个比较让人关心的指标,从图中可以得知,在并发量达到4000的时候,开始有数据丢失的现象,而造成这一现象的原因并非是TwinsBufferPool实现代码的Bug,而是请求超时导致的“Connection refused”,因为每个Servlet运行容器都会有超时机制,如果排队请求时间过长,就是直接被拒绝了。因此,看数据丢失量和错误率曲线,这两者是一致的。如果设置成不超时,那么将是零丢失量,零错误率,所带来的代价就是平均响应时间会拉长。因为受限于个人的测试环境,整个测试过程显得不是很严谨,得出来的数据也并不是很完美,不过,我这里提供了一些优化调整的建议:1、硬件环境。正所谓“巧妇难为无米之炊”,如果提供的硬件性能本身就是有限的话,那么,在上面运行的软件也难以得到正常的发挥。2、软件架构。这个想象的空间很大,其中有一种方案我认为未来可以纳入到RoadMap中:多缓冲池的负载均衡。我们可以尝试在一个应用中启用多个缓冲池,通过调度算法,将缓冲数据均匀的分配给各个缓冲池,不至于出现只有一个缓冲池“疲于奔命”的状况,最起码系统的吞吐量会有所提升。3、其他中间件或者工具的辅助,比如加上消息中间件可以起到削峰的作用,各项指标也将会有所改善。4、参数调优。这里的参数指代的不仅仅是缓冲池的参数,还有包括最大连接数,最大线程数,超时时间等诸多外部参数。四、总结本文详细阐述了双Buffer缓冲池的设计原理,以及实现方式,并对TwinsBufferPool实施了压测,也对测试结果进行了一番分析。欢迎关注我的微信订阅号:技术汇。如果想查看完整的测试报告,可在订阅号内回复关键词:测试报告,即可获取到下载链接。如果想深入研究TwinsBufferPool源码的读者,可在订阅号内回复关键词:缓冲池。 ...

March 28, 2019 · 1 min · jiezi

Nodejs批量下载文件(图片、视频等)

最近在做一个文件下载的功能,这里把做的过程中用的技术和坑简要总结下1. 单文件下载1.1 同源单文件针对单文件的情况下,同源的文件,可以通过 标签的 download 属性下载文件 const elt = document.createElement(‘a’); elt.setAttribute(‘href’, url); elt.setAttribute(‘download’, ‘file.png’); elt.style.display = ’none’; document.body.appendChild(elt); elt.click(); document.body.removeChild(elt);但是这个方案并不适用于非同源的资源,此时它相当于普通的超链接,点击会跳转到资源页面,而不是下载。1.2 非同源图片针对于非同源图片,可以考虑使用 canvas 将图片转换成 base64 编码之后再通过 标签的 download 属性下载function downloadPic(url) { const img = new Image; const canvas = document.createElement(‘canvas’); const ctx = canvas.getContext(‘2d’); img.onload = function() { canvas.width = this.width; canvas.height = this.height; ctx.drawImage(this, 0, 0); const elt = document.createElement(‘a’); elt.setAttribute(‘href’, canvas.toDataURL(‘image/png’)); elt.setAttribute(‘download’, ‘file.png’); elt.style.display = ’none’; document.body.appendChild(elt); elt.click(); document.body.removeChild(elt); }; img.crossOrigin = ‘anonymous’; img.src = url;} ...

March 14, 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

node核心模块-Buffer

什么是bufferBuffer 类是一个全局变量。Buffer 类的实例类似于整数数组,但 Buffer 的大小是固定的、且不与 V8 共用内存。 Buffer 的大小在创建时确定,且无法改变。Buffer.formconsole.log(Buffer.from(‘hello world’));// <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>console.log(Buffer.from([65, 66, 67, 68]));// <Buffer 41 42 43 44>const arr = new Uint8Array(2);arr[0] = 65;arr[1] = 66;const buf = Buffer.from(arr.buffer);console.log(buf.toString(‘utf8’));// ABarr[1] = 67;console.log(buf.toString(‘utf8’));// AC使用字符/数组/数组/arrayBuffer 创建buffer。如果使用 arrayBuffer 创建buffer,arrayBuffer 与 Buffer内存将共享。ArrayBuffer传送门Buffer.allocconsole.log(Buffer.alloc(5));console.log(Buffer.alloc(5, ‘abc’).toString());console.log(Buffer.alloc(5, ‘abcdef’).toString(‘utf8’));console.log(Buffer.alloc(5, ‘见面打声招呼’, ‘gb2312’).toString(‘utf8’));// <Buffer 00 00 00 00 00>// abcab// abcde// 见�填充内容不足的情况下重复填充。填充内容大小超出可用内存大小将被截断;中文按照三个字节来计算,所以上面出现了乱码;不传入填充内容的情况下使用空字符填充Buffer,这里的空字符不是指空格字符;Buffer.allocUnsafeconsole.log(Buffer.allocUnsafe(5));console.log(Buffer.allocUnsafe(5));// <Buffer 60 07 04 03 01>// <Buffer 80 07 04 03 01>等同于 node v6.0.0 之前的 new Buffer();以这种方式创建的 Buffer 的内存是未初始化的。 Buffer 的内容是未知的,可能包含已存在数据。不推荐,如果一定要用,使用需要使用 Buffer.fill 进行填充,或者直接使用Buffer.alloc。Buffer 模块预先分配大小为 8Kb (Buffer.poolSize)的内部 Buffer 池,用来快速分配给新 Buffer 实例。Buffer.alloc 永远不会使用内部 Buffer 池。Buffer 池空间大于一半时,Buffer.allocUnsafe 将优先使用预分配的 Buffer 池,返回一个内存地址,类似于指针概念。Buffer. allocUnsafeSlowBuffer.allocUnsafeSlow();与 Buffer.allocUnsafe 的区别是,不会使用预分配 Buffer池,而是从外部获取一块内存,生成新的 Buffer。可以避免 Buffer池 创建太多的Buffer。Buffer 文件读取const txtPath = path.join(__dirname, ‘./test.txt’);const content = fs.readFileSync(txtPath);// <Buffer 61 62 63 …>console.log(content.toString(‘utf8’));// contentnode 中文件的传输与读取以及写入操作都是有基于 Buffer 进行操作。其他Buffer 还有各种转码,以及读取写入等操作,具体看API 这里不做过多介绍使用场景内容等比切割文件读取传输操作资源临时存储,不如js,css等静态文件…参考资料http://nodejs.cn/api/buffer.htmlhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBufferhttp://guojing.me/linux-kernel-architecture/posts/how-slab-work/ ...

December 19, 2018 · 1 min · jiezi