tcmalloc小记

原文:https://google.github.io/tcmalloc/design.html 之前尝试过探索一下golang的内存分配机制,但是由于其太过于庞大,个人积累也不足,就先放弃了。后来想起来它跟tcmalloc的分配方式很像,所以就单独抽时间看了下tcmalloc的设计思路。 这篇几乎是原文的翻译篇,可能会加入一些个人理解。 tcmalloc的特性 大多数的分配和释放内存都是无竞态的,对于多线程程序来说更加友好。 被释放的内存可以给不同大小的对象进行复用,或者交还给操作系统,在内存使用上更加灵活。 分配相同大小对象的page,从而节省用于管理小对象的内存开销。 提供了解当前内存使用情况的能力,并且采样的开销较小。 概览 下面这张图是tcmalloc内部的概览图。 意思是可以将tcmalloc大致分为三个层次: front-end:提供线程或者cpu级别的cache,这是最快的分配对象方式。 middle-end:负责填充front-end的cache。 back-end:负责向操作系统申请更多的可用内存。 就这么简单一看的话,tcmalloc似乎就是利用了缓存以及线程隔离的思想去加快分配内存。 front-end要么是运行在per-cpu的模式或者传统的per-thread模式,以及back-end要么支持传统页堆或者大页堆。 front-end front-end管理cache,这个cache的里面是待分配内存块,cache每次只会被一个线程访问,因此不需要加锁,分配和释放内存都非常快。 如果外界向front-end请求的内存大小对应的cache为空,那么front-end就会向middle-end去请求一批可用内存去填充cache。middle-cache由CentralFreeList和TransferCache组成。 如果外界向front-end请求的内存大于front-end最大的cache,或者middle-end已经耗尽,那么就会找back-end分配这个大内存块,或者请求填充middle-end。back-end也称为PageHeap页堆。 之前说到front-end的两种模式,分别是per-thread和per-cpu,因为现代大型程序通常包含很多线程,每个线程都要cache,就会导致front-end整体内存占用很大,若要降低front-end的整体内存占用,那么每个线程的cache又会变得太小,导致cache频繁耗尽或者溢出。回想一下tcmalloc要弄per-thread cache的原因只是为了降低并发锁冲突,但现代计算机上如果多个线程运行在同一个cpu上,这些线程实际上是线性交替执行,因此tcmalloc支持了per-cpu的模式,从而降低内存占用的同时又能保证降低并发冲突。 分配小对象和大对象 小对象会由front-end来负责映射到60到80个大小类别中,比如分配一个12字节的对象属于16字节的大小类别。但是如果分配的内存太大,就会由back-end来直接负责此次分配,这种对象的大小与tcmalloc页大小对齐。 原文还说了一些关于小对象内存分配对齐的事情,这里跳过。 释放内存 关于内存的释放这块感觉还是挺复杂的。 首先,如果释放的内存大小是编译时未知的,tcmalloc就要去查找pagemap。如果释放的是小对象,就会被归还到front-end的cache中,否则就会被归还到back-end的页堆中。 per-cpu模式 在这个模式中,front-end管理着一大块内存(称为slab) 一图胜千言,slab被分为多个cpu块交给不同的cpu管理,我们将cpu块称为cpu本地缓存。本地缓存中的左边存储元数据,右边存储指向可用对象的指针数组。 元数据中有三种指针,分别指向对应大小类数组中的开始位置、当前分配到的位置、当前最大位置。另外,还有一个全局的“静态最大容量”,限制了当前最大位置减去数组开始位置不能大于这个“静态最大容量”。虽然没看过内部实现,但盲猜这个静态最大容量是硬编码的,并且“开始位置指针”和“当前最大位置指针”在整个cpu块上是动态变化的,目的是尽量高效地提高内存利用率。 对象都从本地缓存中获取或者归还,当获取时如果本地缓存不够用就会找middle-end拿,当归还的时候如果本地缓存溢出则溢出到middle-end中。 当某个大小类别的对象已经分配完,tcmalloc会尝试增大该大小类别的容量,直到达到该大小类别的静态最大容量或者整个cache的最大容量。为了增加容量,该大小类别甚至可以从同一个cpu块的其它大小类别中“偷”容量。 front-end限制的是单个cpu本地缓存的大小,那么如果机器的cpu越多,front-end就占用越多内存。为了节省内存,当程序不再使用某个cpu时,tcmalloc支持释放该cpu缓存中的内存。 rseq tcmalloc的per-cpu模式依赖于linux的rseq系统调用。rseq可以理解成原子指令序列,如果这个指令序列执行到一半发生了上下文切换,那么下一次会重新从头开始执行。该序列要么不间断地完成,要么反复重新启动,直到不间断地完成,这个过程是不需要锁的,从而避免了序列的争用。 那么rseq对于per-cpu模式的意义就在于,在无需使用锁的情况下就读写per-cpu数组的元素。 这部分更具体的设计参考https://google.github.io/tcmalloc/rseq.html per-thread模式 在这个模式中,每个线程都有自己的本地缓存。每个本地缓存中的数据结构组织如图所示,每个大小类的空闲对象用单链表进行组织。 当线程结束的时候,该线程的本地缓存归还给middle-end。 front-end运行时动态扩缩容 这个小节介绍了front-end在运行时的内存占用不是一成不变的,而是会根据实际情况发生大小变化,涉及到调优的时候可能可以看一下:https://google.github.io/tcmalloc/design.html#runtime-sizing-of-front-end-caches middle-end middle-end负责解耦前后端,包括给front-end提供内存以及向后端归还内存。在middle-end中,每个大小类内存管理由一个传输缓存(transfer cache)和一个中央空闲链表(central freelist)管理。 传输缓存 总结

三月 16, 2025 · by NOSAE

请不要再称数据库是CP或者AP (Please stop calling databases CP or AP)

Note 文章转载自https://blog.the-pans.com/cap/ 其它参考: quorum-rw 后分布式时代: 多数派读写的’少数派’实现 经Martin Kleppman本人同意,这篇文章是他英文原文的中文翻译。Authorized by Martin Kleppmann, this is a Chinese translation of his original blog post. ...

十一月 17, 2024 · by NOSAE

向量数据库概述

Note 文章转载自https://guangzhengli.com/blog/zh/vector-database/ 也许你最近可能听过这样的新闻,某向量数据库的初创公司刚写好 PPT,就获得了几千万的投资,某公司的开源的向量数据库因其代码的简陋而登上了 Hackernews 等等。在过去几个月时间中, AI 应用的发展如火如荼,带动了 AI 应用技术栈上下游的火爆,而向量数据库就是其中最热门的之一。 ...

十一月 16, 2024 · by NOSAE

分布式事务综述

理论知识 事务的四个特性:ACID Atomic 原子性:一个事务中的所有操作,要么全部完成,要么全部不完成 Consistency 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。完整性包括外键约束、应用定义等约束不会被破坏 Isolation 隔离性:防止多个事务并发执行时由于交叉执行而导致数据的不一致 Durability 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失 分布式系统三个特性:CAP Consistency 一致性:集群执行某个操作后,所有副本节点的状态都相同,那么这样的系统就被认为具有强一致性 Available 可用性:集群一部分节点故障后,还能对外提供服务 Partition tolerance 分区容忍性:狭义上是集群节点之间是否能正常通信,更广义的是对通信的时限要求,系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况 对于互联网的场景来说,一般选择AP:因为现在集群规模越来越大,主机众多、部署分散,所以节点故障、网络故障是常态,而且要保证服务可用性达到N个9,即保证P和A,舍弃C。 分布式的BASE理论:柔性事务,对CAP的权衡 Basically Available 基本可用:系统在出现不可预知故障的时候,允许损失部分可用性,但这绝不等价于系统不可用 Soft state 软状态:允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性 Eventually consistent 最终一致性:经过一段时间的同步后,最终能够达到一个一致的状态 NewSQL的分布式事务: 以Spanner、TiDB为代表的NewSQL,在内部集群多节点间,实现了ACID的事务,即提供给用户的事务接口与普通本地事务无差别,但是在内部,一个事务是支持多个节点多条数据的写入,此时无法采用本地ACID的MVCC技术,而是会采用一套复杂的分布式MVCC来做到ACID。属于满足CP的系统,同时接近于满足A,称之为CP+HA。但是NewSQL和BASE的系统之间,性能上差异可能是巨大的。 跨库跨服务的分布式事务:这类分布式事务部分遵循 ACID 原子性:严格遵循 一致性:事务完成后的一致性严格遵循;事务中的一致性可适当放宽 隔离性:并行事务间不可影响;事务中间结果可见性允许安全放宽 持久性:严格遵循 现有的分布式事务方案都无法做到强一致,但是有强弱之分:XA事务 > TCC > 二阶段消息 > SAGA(一般情况下)。具体为: XA:XA虽然不是强一致,但是XA的一致性是多种分布式事务中,一致性最好的,因为他处于不一致的状态时间很短,只有一部分分支开始commit,但还没有全部commit的这个时间窗口,数据是不一致的。因为数据库的commit操作耗时,通常是10ms内,因此不一致的窗口期很短。 TCC:理论上,TCC可以用XA来实现,例如Try-Prepare,Confirm-Commit,Cancel-Rollback。但绝大多数时候,TCC会在业务层自己实现Try|Confirm|Cancel,因此Confirm操作耗时,通常高于XA中的Commit,不一致的窗口时间比XA长 MSG:二阶段消息型事务在第一个操作完成后,在所有操作完成之前,这个时间窗口是不一致的,持续时长一般比前两者更久。 SAGA:SAGA的不一致窗口时长与消息接近,但是如果发生回滚,而子事务中正向操作修改的数据又会被用户看到,这部分数据就是错误数据,容易给用户带来较差的体验,因此一致性是最差的。 2PC 两阶段提交流程如下: 准备阶段:RM去开启事务并执行操作,但是不提交事务,此时相关资源都被锁定,第三方不能访问这些资源。返回操作结果给TM 提交阶段:若所有RM在准备阶段都操作成功,TM将发出请求让所有RM提交事务,否则发出请求让所有RM回滚 存在问题: 同步阻塞:从准备阶段的锁定资源,直到事务提交/回滚的过程中,资源都处于被RM锁定的状态,第三方访问会被阻塞。 单点故障:2PC的推进重度依赖TM,TM若发生故障,RM会被阻塞。 数据不一致:若提交阶段部分RM无法与TM正常通信,导致一部分子事务提交了,而发生异常的RM没提交,全局事务发生不一致。 3PC 3PC提交将2PC的准备阶段再次拆分,加入检查阶段,如果检查失败的话马上abort,减少了2PC在失败情况下白白浪费资源,并且引入RM的超时机制(2PC只有TM超时机制),如果在提交阶段TM超时,直接提交事务释放资源。3PC流程如下: 检查阶段:TM 询问 RM,是否具备执行事务的条件,RM 进行自身事务必要条件的检查 预提交阶段:TM 通知 RM 进行事务的预提交 提交阶段:TM 根据预提交阶段 RM 的反馈结果通知 RM 是否进行事务提交或是进行事务回滚 RM超时自动提交是因为此时已经进入了提交阶段,说明RM知道检查阶段已经成功(但是RM不知道预提交阶段是否成功),认为第三阶段很大概率也可以成功。但很明显,如果第二步存在RM失败了,即预提交失败,因此在第三步中,TM发出回滚请求,但此时又有另外一个RM超时并自动提交了事务(超时时间内收不到回滚请求),就会出现不一致。 ...

十月 28, 2024 · by NOSAE

clash规则配置

我一直在用的wmsxwd抑或是一元机场,这些厂商自带的代理规则都太少,对于没匹配上规则的网站,只能要么全部走代理或者全部直连,搞得我经常要手动切换。所以我需要一个相对更加全面的规则列表,让该走代理的网站走代理,该直连的直连!!! 无意中翻到clash-rules这个每日更新代理规则的仓库,便拿来用之。 使用方式如下: 去机场厂商那里拿到订阅链接,注意有的厂商不会直接给订阅链接而是托管链接,并且会给你一个网站自己去转换成订阅链接 创建一个.yaml文件(文件名随便),不同的客户端可能配置文件位置不同,比如我是放在~/.config/clash目录下 编辑文件内容,其中一些配置用原来默认的就行,比如我的是 mixed-port: 7890 allow-lan: true bind-address: '*' mode: rule log-level: info external-controller: '127.0.0.1:9090' 配置proxy-providers配置,顾名思义,配置代理 proxy-providers: CNIX: # 代理名称,自定义即可 type: http # 以远程下载的方式获取代理 url: "订阅链接" # 填写你的订阅链接 path: ./cnix.yaml # 从订阅链接下载存到本地的cnix.yaml文件 interval: 86400 # 更新间隔(s) 配置rule-providers,顾名思义,配置代理规则,同样以远程的方式获取,并定时更新,参考仓库的教程即可,比如加入一个名为google的远程规则(不知道为什么jsdelivr我经常访问不了,所以就直接不走这个cdn了): rule-providers: google: type: http behavior: domain url: "https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/google.txt" path: ./ruleset/google.yaml interval: 86400 配置proxy-groups,CNIX就是刚刚配置的proxy-providers,这里定义一个代理组去使用它 proxy-groups: - name: PROXY type: select use: - CNIX 配置rules,将规则和代理组关联起来。除了PROXY以外,DIRECT和REJECT可视为默认的代理组,即直连和拒绝,其中拒绝网站通过查看reject.txt就能看到,包含一些广告网站或者人家的内部网站、垃圾网站等。想访问某个网站一直被拒而关掉clash后又能访问的话,可以看看是不是reject.txt的锅 ...

十月 21, 2024 · by NOSAE