papers-notebook
papers-notebook 是一个专注于计算机科学领域的论文阅读笔记仓库,主要涵盖分布式系统、虚拟化技术、机器学习及安全等前沿方向。该项目旨在解决科研人员和开发者在阅读海量学术文献时难以系统化梳理核心思想、实现细节及个人评价的痛点。作者将每篇论文的笔记精炼在千字以内,不仅记录了如 Mesos、Borg、Raft 等经典架构的设计思路,还包含了对技术演进的独到见解。
不同于传统的静态文档维护方式,papers-notebook 创新性地利用 GitHub Issue 页面进行内容管理与社区互动,鼓励用户分享值得研读的论文并参与讨论。这种开放协作的模式让知识库得以持续更新和丰富。该工具特别适合计算机专业的研究生、高校研究人员以及从事底层系统开发的工程师使用,既能作为入门特定领域的学习路线图,也能作为资深从业者回顾经典算法与架构的速查手册。通过沉淀高质量的阅读思考,papers-notebook 帮助用户高效构建专业知识体系,激发新的研究灵感。
使用场景
一位专注于分布式系统优化的研究生,正在为设计新型集群调度器而密集研读 Mesos、Omega 及 Borg 等经典论文。
没有 papers-notebook 时
- 知识碎片化严重:阅读过的论文想法零散分布在本地不同的 Markdown 文件或纸质笔记中,缺乏统一索引,难以快速回顾如“两层调度”等核心架构演进。
- 深度思考缺失:往往只记录了论文的大致摘要,忽略了实现细节与个人批判性评价,导致在复现或对比算法时无法调用当时的灵感。
- 交流协作困难:面对庞大的文献列表,无法高效地向导师或同门分享值得精读的篇目,也难以在特定议题(如锁服务或一致性协议)上形成结构化的讨论基础。
- 篇幅控制失控:笔记容易写成冗长的翻译稿,缺乏"1000 字以内”的精炼约束,导致复习成本极高,抓不住重点。
使用 papers-notebook 后
- 体系化知识沉淀:利用其按分布式、虚拟化及安全等领域分类的目录结构,将 Mesos 到 Firmament 等调度器笔记有序归档,一键定位架构演变脉络。
- 洞察即时固化:遵循项目规范,在记录 Idea 的同时强制写入实现方式与个人评价,确保每一篇笔记都包含独特的技术洞察而非单纯复述。
- 社区化协同增效:通过 GitHub Issue 页面管理笔记并分享推荐清单,轻松与社区互动,快速获取他人对特定论文(如 TaintDroid 或 Raft)的深度解读。
- 高效精炼输出:受限于千字篇幅要求,迫使研究者提炼核心逻辑,生成的笔记既适合快速复习,也便于作为技术博客的素材库。
papers-notebook 将孤立的论文阅读转化为结构化、可协作的知识资产,极大提升了从理论研读到系统设计的转化效率。
运行环境要求
未说明
未说明

快速开始
论文笔记
这个 repo 希望能够记录自己阅读论文的过程,其中的论文一部分来自于在上海交通大学软件学院的研究生课上需要阅读的论文,这部分会比较偏安全和虚拟化。还有一部分论文是自己感兴趣,想去了解的,这部分可能比较偏虚拟化和分布式。论文笔记希望能够记录自己在读论文的时候的想法,其中包括但不限于论文的大致 idea,实现方式,以及自己对论文的评价等等。希望能够把每一篇论文的笔记限制在 1000 字以内。
目前不再以 Markdown 文件的方式维护,转到 issue 页面进行,也欢迎到这个 issue 中分享您认为值得一读的 paper
目录(TOC)
- 分布式(Distributed System)
- 虚拟化(Virtualization)
- 沙箱(Sandboxing)
- 系统(System)
- 安全(Security)
- 大数据
- 网络
Created by gh-md-toc
分布式(Distributed System)
调度器(Scheduler)
在我看来,分布式是研究如何让程序能够在多台机器上运行拥有更好的性能的一个方向。那如果要实现这一点,调度很关键。
目前我读过的与分布式调度器有关的论文有 Mesos, Omega, Yarn 和 Borg。这其中 Mesos 是最早的,它来自伯克利大学。其最亮的地方在于两层调度的框架,使得调度跟框架是松耦合的。目前很多公司都是在用它的,而且有很多基于 Mesos 的创业公司,比如 Mesosphere 等等。Omega 跟 Mesos 的第二作者是一个人。Omega 具体来说也不知道算是谁写的,应该算是大学和谷歌一起做的研究。Omega 发表在 EuroSys'13 上,是在基于 Mesos 的基础上,提出了一种完全并行的调度的解决方案,能够在调度上有更好的性能。不过论文是采取的模拟实验来进行验证的,不知道是否有生产上的使用。Borg 是发表在 EuroSys'15 上的,是谷歌真正一直在使用的集群管理工具。可以说谷歌为什么可以用廉价的机器来达到很高的可用性,有很大一部分是因为 Borg。Borg 和 Omega 是不开源的,而 Mesos 是开源的,不过 Borg 有一个开源的继任者,就是目前大名鼎鼎的 Kubernetes。Kubernetes 下面用 Docker Conatainer,而不是 Linux 内核的那些用来做进程级别的性能隔离的特性来实现的。不过最近 Kubernetes 似乎在跟 Docker 撤逼,因为 Docker 公司的一些强势吧,可能之后不会只支持 Docker Conatainer。Kubernetes 在很长的时间内都不是 production ready 的,只能支持 100 个节点,跟 Borg 的 10K 完全没法比,不知道现在是什么情况。
Mesos
Mesos 是调度器绕不过去的一篇论文,因为它是目前在工业界应用最广泛的开源系统之一。Mesos 跟 exokernel 的思想很像,分离了管理和保护。之前所有的调度器,都是由自己来决定是否把资源给某个任务的,在 mesos 中是 framework 决定是否接受资源的要约。在容灾上,mesos 是通过 zookeeper 做 leader election 的,而且 master 维护的状态是 soft 的,因此 master 没有单点故障的问题,而对于 node 的故障,会反馈给 framework,让他们来处理。Mesos 就像是一个最小的 kernel,所有的决定都是『用户态』的 framework 来做的。
Mesos 是在 node 上给每一个 framework 运行一个 executor 的,使用 Linux Containers 等容器技术来做隔离。最后通过在一个集群上跑多个 framework 的方式来验证 mesos 的可用性。这篇文章整体来说比较简单,也可能是因为听过太多次关于 mesos 的架构了。
Omega
Omega: flexible, scalable schedulers for large compute clusters
// TODO Add the notes
Borg
Large-scale cluster management at Google with Borg
Borg 是谷歌发表在 EuroSys'15 上的一篇文章,讲述了其内部是如何做集群管理的。Borg 是我看过的第一篇关于集群管理的论文。首先介绍下 Borg 的特点,Borg 是一个用来做集群管理的工具,它的目标就是让跑在它上面的应用能够拥有很好的可用性和可靠性的同时,能够提高机器的利用率,而且这些是在一个非常大规模的机器环境下。在 Borg 被设计的时候,还没有对虚拟化的硬件支持,也就是 Intel VT-x 等等那些硬件特性,所以 Borg 是使用了进程级别的隔离手段,也就是 Control Group。
Borg 的架构其实还挺简单的,是比较经典的 Master/Slave 架构,其中在 Master 部分,有两个抽象的进程,一个是 Borg Master,一个是 Borg Scheduler。Borg Master 是有5个备份的,每个都会在内存里维护集群里所有对象的状态。他们5个组成了一个小集群,用 Paxos 算法做一致性的,会选举出一个 leader 来处理请求,这点是之前 Kubernetes 做不到的。
调度器方面的实现比较简单,就是一个队列,根据优先级做 round-robin。这里读起来感觉没什么新意,就不多说了。调度的平均时间大概是 25s,其中 80% 的时间在下载包。谷歌也是实诚,下载安装包的时间都算到调度里面去。
谷歌写的论文一向是简单易懂,特别良心的。所以要是对集群感兴趣,可以去看下这篇论文,花两三个小时就能看完了。
Yarn
Apache Hadoop YARN: Yet Another Resource Negotiator
// TODO Wait to read
Sparrow
Sparrow 是一个与前面的调度器架构都不一样的实现,是去中心化的架构。之前的所有调度器,无论是 monolithic 的还是后面 Mesos 那样两层的架构,都是有一个中心化的调度器在运行,这样的方式会使得调度器的效率不是那么高。 Sparrow 是 AMPLab 的又一力作,发表在 SOSP'13 上,它不是一个 general-purpose 的调度器,而是针对 short job 这一特殊的 workload。其灵感来源于一个负载均衡方面的经典论文,k choices。这篇文章的 idea 是,在 k 台机器里选一个最好的,而不是在 n 里选一个最好的,可以大大降低负载均衡的 overhead,同时也对负载的分配跟最优解差不了多少。
Sparrow 的核心思想是:在分配任务时,随机选择几个工作节点,然后从中挑选出最合适的一个。这是因为 Sparrow 的应用场景中,所有任务都非常短小,因此只需考虑任务队列的长度即可。针对多个任务的分配问题,Sparrow 采用了批处理的方式来优化。例如,当有 2 个任务需要分配时,它不会分别进行两次选择,每次从 k 个工作节点中选出 1 个,而是直接从 2k 个工作节点中一次性选出 2 个。
这篇论文读起来非常轻松,其思路也相对简单,但却与以往的方法完全不同。其实这种想法并不难想到——在阅读“k 选 1”的相关文献时,我就曾思考过是否可以将其应用到调度器中,结果发现这一思路早已被提出。
目前,Sparrow 已在 GitHub 上开源,但尚未听说有公司将其应用于生产环境。它的主要局限性在于对工作负载的适应性较弱,仅对短任务场景表现良好。
Apollo
// TODO 等待阅读
Hawk
寻找新思路的一种方法,就是在两种极端方案之间找到折中点,实现你中有我、我中有你的结合。毕竟计算机领域的几乎所有问题都涉及权衡。两种极端方案往往是为了满足不同的需求而设计的,而折中方案则常常是一种巧妙的创新方式。类似的例子包括单内核、微内核和混合内核。本文也是如此,它将去中心化和中心化调度相结合。前面提到,去中心化的调度方式只适用于短任务的工作负载;而在 Hawk 中,长任务由中心化调度器处理,短任务则由去中心化调度器负责。
此外,为了使这两种调度方式更好地协同工作,Hawk 还做了一些细节上的优化。例如,当某个工作节点完成手头的所有任务后,会主动“抢”其他节点上的短任务来执行。看来资本主义下的工作节点确实都很积极进取。
值得一提的是,这篇论文的验证使用了谷歌公开的追踪数据集,该数据集同样开源,可在 https://github.com/google/cluster-data 找到。
Mercury
Mercury 与 Hawk 提出的思路几乎完全相同,都是在总结集中式和分布式调度各自的优缺点后,提出了一种混合方案。Mercury 由微软提出,其演示文稿制作得更为精美。它基于 YARN 实现,而 Hawk 则是在 Spark 上实现的。这说明,一篇论文若要真正具有说服力,必须要有具体的实现和实验验证。
Firmament
这是一篇基于最大流最小割算法的集中式调度论文。本文借鉴了 Quincy 的研究,但在求解最大流最小割问题时,进行了特定于问题的优化,从而解决了 Quincy 无法克服的效率瓶颈。此外,Firmament 还并行运行两个 MCMF 算法,以进一步加速调度过程。该项目已在 Kubernetes 中实现了部署。
ERA
// TODO 添加笔记
集群调度中的高效队列管理
// TODO 添加笔记
Tarcil
// TODO 添加笔记
Eagle
// TODO 等待阅读
Canary
// TODO 等待阅读
仓库级计算机的性能剖析
// TODO 等待阅读
图计算
Wukong
Wukong 采用去中心化的设计,每个服务器都可以独立处理请求。每个服务器分为两部分:查询引擎和存储模块。
Wukong 共有两种索引类型:Type 和 Predicate。
现有的 RDF 系统存在哪些瓶颈?
现有实现主要有两种:Triple store and triple join 以及 Graph store and graph exploration。前者将 RDF 数据以三元组的形式存储在关系数据库中,查询过程分为扫描和连接两个步骤。扫描会分解为多个子查询,最后再通过哈希连接等操作将结果合并在一起。由此可见,当数据量非常大时,最终的连接操作将成为一大难题。
第二种方式则是以图结构存储和查询 RDF 数据,典型的代表是 Trinity.RDF,它引入了一些剪枝优化技术。然而,最终仍然需要进行一次完整的连接操作。
综上所述,现有的实现中,最终的连接操作是最主要的瓶颈。
Wukong 与之前的基于图的设计相比有哪些不同?有何优势?
最大的区别在于索引的存储方式。以往的基于图的设计通常使用独立的索引数据结构来存储索引,而 Wukong 则将索引本身视为基本的数据结构(即节点和边)来存储,并且还考虑了分区存储的方式。
这样做有两个好处:首先,在进行图遍历或搜索时,可以直接从索引节点开始,无需额外的操作;其次,这种方式使得索引的分布式存储变得非常简单,直接复用了常规数据的存储方法。
什么是全历史剪枝?与之前的剪枝方法相比有何不同?为什么 Wukong 可以采用全历史剪枝?
所谓“全历史”,就是指会记录下所有的历史信息。而以往的做法只是记录一次。之所以能够这样做,一方面是因为 RDF 查询的步骤通常不多,另一方面,RDMA 在处理小于 2KB 的数据时性能几乎不变,因此 Wukong 可以采用这种方法。
锁服务
Chubby
// TODO 等待阅读
一致性(Consensus)
Raft
这篇论文以其易读性和易实现性而广受认可,但这只是相对于 Paxos 而言的。实际上,要想真正读懂它,仍需具备一定的背景知识。由于我尚未正式研读过 Paxos,因此难以对其进行比较。不过,Raft 借鉴了状态机的思想,将分布式一致性问题拆分为领导者选举和日志同步两个子问题,这种做法简洁明了。
我认为理解 Raft 并不困难,但要弄清楚其正确性是如何得到保证的,则相对复杂。越是简洁的系统,其背后的数学证明往往越复杂,而这一点我目前还不甚了解。
Zookeeper
// TODO 等待阅读
Paxos
// TODO 等待阅读
存储(Storage)
BigTable
表、表格和 SSTable 之间是什么关系?
表是用户可见的数据表,包含行、列以及不同版本的值。为了便于存储,表在逻辑上被划分为多个表格。
表格是一个存储单元。在 BigTable 的架构中,由一个主服务器和多个表格服务器组成。表格这一抽象概念类似于传统数据库中的分片思想,与 HBase 中的区域概念相似。表格的读写操作由表格服务器负责。
SSTable 是一种文件的格式,全称是 "Sorted Strings Table",是指按照 Key 的排序在文件中存储 <Key, Value> 对。在逻辑上 SSTable 会包含多个 Block,每个 Block 为了方便寻址会有一个 index,所有的 Block index 会写在 SSTable 文件的最后,每当 SSTable 文件被 open 的时候,会将索引加载到内存里,这样每次 Lookup 的时候只有一次硬盘读取。SSTable 也支持全部读取到内存里,这样在 Lookup 的时候没有任何硬盘的读取。这个跟 HBase 中 HFile 的文件格式实现很相似。
描述当读操作或写操作到达时会发生什么。
读取和写入是以 Tablet 作为一个 Unit 进行的。
在进行写操作的时候,Tablet Server 会先做一些检查,保证请求的合法以及权限问题。权限的检查是通过检查一个在 Chubby 中的列表进行的,这个列表会被 Client Library 缓存住。一次被允许的写操作会先进入 Commit Log,在处理 Log 的时候采取了批处理来提高吞吐。在操作被 Commit 后,它的内容会被插入 MemTable 里,当 MemTable 的 size 超过一个阈值的时候,会让当前的 MemTable 进入一个 frozen 的状态,随后创建一个新的 MemTable,Frozen 的 MemTable 就可以以 SSTable 的形式写入 GFS。
在进行读操作的时候,Tablet Server 会在做了一些检查保证合法后,在 MemTable 和 SSTable 的一个 merge 后的 view 中来进行读操作,这样可以保证可以读到最新的值。
描述 BigTable 适合哪些应用,而不适合哪些应用。
BitTable 适合那些对可用性要求比较高的业务场景,同时对于跨行的事务性没有要求的应用。但是正因为没有跨行事务的支持,所以我觉得引用场景很局限。目前在谷歌内部应该也逐渐被 Spanner 和 F1 所取代吧。
Dynamo
Dynamo: Amazon’s Highly Available Key-value Store
// TODO 等待阅读
Spanner
Spanner: Google’s Globally-Distributed Database
什么是外部一致性?外部一致性与可串行化有什么区别?
文中对外部一致性的描述是:如果事务 2 发生在事务 1 提交之后,那么事务 2 的时间戳要比事务 1 提交的时间戳要大,也就是线性一致性。
可串行化是数据库隔离性的一个级别,这意味着数据库中所有的事务都是可以被序列化来执行的,只有完全没有冲突的事务才可以并发地执行。
外部一致性是一个关于一致性的概念,在分布式场景下,外部一致性更难实现,因为时序对时间的精度要求很高,在分布式场景下,有可能出现因为不同机器系统时间不一致导致事务 2 拿到一个比事务 1 提交的时间戳更小的时间戳。
可串行化是隔离性上的概念,如果做到了外部一致性,就一定可以做到可串行化。
Spanner 是如何实现外部一致性的?
Spanner 之前的 Percolator 和 Spanner 都是使用全局的时钟来解决外部一致性的问题。但是 Spanner 创新地使用了原子钟和 GPS 来作为全局的时钟,以此来实现外部一致性。
在事务的执行中,Spanner 会保证,每个事务的 commit timestamp 都会在其 start 和 commit 之间。Spanner 依赖的底层容器集群系统 Borg 会维护一个 True Time API,这个 API 会返回精度为 ε 的时间区间 [t - ε, t + ε]。因此每个事务会在 start 和 commit 的时候分别调用一次 True Time API,拿到两个时间区间 [t1 - ε,t1 + ε] 和 [t2 - ε,t2 + ε],因此在区间 [t1 + ε,t2 - ε] 之间的时间都是可用的,如果 t1 和 t2 很接近,那最多需要等 2ε。
如果 TrueTime 假设被违反,会发生什么?作者如何论证 TrueTime 假设应该是正确的?
这会导致 True Time API 不能再用来保证外部一致性,文章中提到,CPU 造成的错误比时钟问题多六倍,因此与时钟造成的错误相比,硬件造成的错误微不足道,可以被视为是值得信任的。
分布式事务与 RDMA 和 HTM
DrTM 如何检测远程读取和本地写入之间的冲突?
在检测事务的冲突上,DrTM 使用了 HTM 和 RDMA 两种技术,HTM 是一个硬件的特性,在硬件级别提供了有限的事务性内存的支持。RDMA 是 Remote Direct Memory Access,提供了远程直接访问内存,不阻塞 CPU 的操作。
在处理事务冲突时,是在 transaction layer 做的。对于远程的读和本地的写操作引起的冲突,最简单的方法是用 RMDA 锁住一个 remote record,不管是读还是写。但是这样会大大降低并行性,因此文章进行了一些改进,引入了基于租约的锁,来保证读共享。而在读和本地写产生冲突时,读会通过 RDMA 抑制本地的 HTM 事务,从而避免冲突。
为什么 DrTM 会出现死锁问题,它是如何避免死锁的?
首先,在 DrTM 的 fallback 处理程序中,不能像传统的实现那样,一个简单的锁就可以解决问题,而是 fallback 处理程序通过 2PL,对于任何 record 都是以远程的形式进行访问。这里就有可能产生死锁,因为涉及到远程锁的顺序。
为了避免这个问题,DrTM 声明了一个全局的释放和申请锁的顺序,避免了死锁的问题。
DrTM 的局限性是什么?
首先,DrTM 没有做到很好的可用性,这是它最大的局限性。还有就是需要硬件特性的支持,导致在很多现有的硬件上没有办法完全复刻 DrTM 的工作,而需要一些适配性的工作。
基于 RDMA 的键值存储
什么使得内存中的键值存储系统适合进行 RDMA 优化?
因为内存中的键值存储系统的大多数请求都是读操作,因此对于 RDMA 来说,这样的特点使得其实现比较简单,只需要对 get 请求做修改就可以了,这样既可以利用 RDMA 的优点,又不需要对系统做过多的修改。
Pilaf 是如何确保 ‘get’ 操作的数据一致性的?它是否存在任何问题?
Pilaf 利用了『自校验』的数据结构,它包括一个 root 和很多 pointer,然后会记录一个 checksum。client 通过检查 checksum 可以检测到读写不一致。当遇到了数据竞争时,client 会自动地进行重试操作。
文中提到有两个应用场景会有问题,一个是服务器修改 hash table 的时候客户端也在读 hash table,这会导致客户端从不合法的内存地址读取内容。
另外是客户端的指针引用可能非法的。比如当服务器在删除一个 key-value 对的时候,客户端自己维护的引用就会已经是失效的。
为什么 Pilaf 需要两次往返才能完成一个 ‘get’ 操作?我们能否利用现有的 RDMA 操作将往返次数减少到一次?为什么/如何?
这是因为涉及两次读操作,一次是读哈希表,一次是读真正的 key-value 的内容。可以考虑合并两个内存块,但是这样应该会使得可以存储的空间变小。
基于 RDMA 的 RDF 存储
现有的 RDF 系统有哪些瓶颈?
现有主要有两种不同的实现方式,分别是 Triple store and triple join 和 Graph store and graph exploration。前者是以 triple 的方式来将 RDF 数据存储在关系型数据库中,因此查询有两个步骤,scan 和 join。scan 会分为子查询,最后再借由 hash join 之类的 join 的操作将查询的结果 join 在一起。由此可知如果数据非常大的时候,最后的 join 会是很大的问题。
第二种方式是以图的方式来存储和查询 RDF。这样的方式以 Trinity.RDF 为代表,有一些剪枝的优化。但是最后也会有一个 final join 的过程。
纵观之前的实现,最后的 join 是一个最大的问题。
Wukong 与之前的基于图的设计相比有哪些不同?有哪些好处?
最大的不同在于索引的存储方式。之前的基于图的设计都是用独立的索引数据结构来存索引,但是 Wukong 是把索引同样当做基本的数据结构(点和边)来存储。并且会考虑分区来存储这些索引。
这样做有两个好处,第一点就是在进行图上的遍历或者搜索的时候可以直接从索引的节点开始,不用做额外的操作。第二点是这样使得索引的分布式存储变得非常简单,复用了正常的数据的存储方式。
什么是全历史剪枝,它与之前的剪枝方法有什么区别?为什么 Wukong 可以采用全历史剪枝?
全历史剪枝就是说所有的历史记录都会被记录下来。之前是只记录一次的。之所以可以这样做是因为一方面 RDF 的查询都不会有太多步,而且 RDMA 在低于 2K bytes 的时候性能都是差不多的,所以 Wukong 可以这样做。
Ambry
Ambry 是一个针对 Media 的分布式对象存储系统,是 LinkedIn 做的一项工作。LinkedIn 作为一个社交产品,会有很多媒体数据需要处理,在之前是采取了文件系统存数据,Oracle 存元数据的方法。随着规模的扩大发现不行,于是就有了这个系统。
Ambry 是一个比较中规中矩的分布式系统,比较让人印象深刻的只有一些 threshold。因为社交数据有一个特性:越是冷的数据会越冷,因此 Ambry 针对这个观察做了一些优化,同时在负载均衡上用了 threshold + round-robin 的方式,很 simple 但是效果不错。除此之外在数据存储上有分层的概念在里面,索引是逐层进行的。最上面是 bloom filter,然后是顺序的 segment,最下面是 partition 里真实的数据。
文章很简单,算是很多工程上的点拼在了一起写的论文,有点像最近读的 F2FS,此外 Ambry 感觉也参考了很多文件系统的设计,有一些共性在里面。比如 log structured update 和 journal 等等。
虚拟化(Virtualization)
虚拟机管理器(Hypervisor)
Xen
Xen是一个非常著名的Hypervisor,它提出了半虚拟化(para-virtualization)的思想。在此之前,虚拟机的实现主要采用全虚拟化(full-virtualization)的方式,但当时的x86架构并不完全支持这种模式。例如,某些指令本应在VMM中执行,但由于不同ring权限级别的行为差异,这些指令有时无法被成功截获并传递到VMM中。为了解决这一问题,Xen引入了hypercall机制,并对Guest操作系统进行了修改,从而更优雅地处理这类情况。
虚拟化的核心资源包括CPU、内存和I/O,而Xen在这三个方面都有其独特之处。其中最引人注目的是它对设备的支持:通过引入Domain 0以及前后端驱动的设计,不仅使设备管理更加自然,还避免了将驱动直接置于VMM中可能引发的稳定性问题。
kvm
// TODO 添加笔记
容器(Container)
mbox
// TODO 添加笔记
Slacker
// TODO 等待阅读
沙箱(Sandboxing)
沙箱与容器之间有着密切的联系。要实现容器,就必须具备隔离能力,而沙箱正是专门用于实现隔离的技术。之所以将两者分开介绍,是因为沙箱本身是一个非常复杂的研究方向,包含多种不同的类型,而容器只是利用了沙箱技术中的部分方法。
沙箱技术大致可以分为两类:第一类是基于隔离的沙箱,它将应用程序的执行环境与操作系统隔离开来,形成一个独立的运行环境;第二类则是基于规则的沙箱,这类沙箱并不完全依赖于严格的隔离,而是通过设定规则来控制每个应用的权限,允许不同沙箱之间共享操作系统的逻辑资源。
我所阅读的论文大多属于第一类沙箱,其中涉及的主要技术包括capabilities、系统调用拦截(system call interposition)以及基于软件的故障隔离(software-based fault isolation)等。
上述链接是一篇综述性质的文章,主要介绍了在Linux平台上可用于实现沙箱的内核特性。文章对沙箱的定义和功能进行了简单易懂的阐述,值得一读。
系统调用拦截(System Call Interposition)
顾名思义,系统调用拦截是指对系统调用进行拦截和过滤的技术。在沙箱的实现过程中,系统调用扮演着至关重要的角色。如何确保应用程序只能执行被授权的系统调用,正是这一研究方向的核心任务。
Janus
其中第一篇论文发表于1996年,是系统调用拦截领域最具经典意义的文献之一。该论文提出了一种名为Janus的系统,能够根据用户定义的策略对应用程序的系统调用请求进行过滤。后两篇则是后续关于Janus的补充研究。
当时互联网刚刚兴起,用户可以直接使用本地的辅助应用程序打开网络上获取的内容。由于这些内容本身不可信,因此辅助应用程序也存在潜在的安全风险。为此,Janus旨在对这类应用程序进行限制和隔离,使其仅拥有最小的权限,从而在遭受恶意攻击时不会影响整个操作系统。
Janus的目标主要有三点:首先是安全性,其次是灵活性,即要求能够对系统调用的限制精确到参数级别——例如,某些特定参数下的open调用可以允许,而其他参数则不行;最后是可配置性,允许为不同应用程序设置不同的策略。
Janus的实现相对简单,主要借助内核中的ptrace功能,开发了一个内核模块和一个用户态引擎。启动时,Janus会先读取策略文件,然后创建子进程,父进程则负责监控子进程的各种事件。子进程会执行辅助应用程序的逻辑,当遇到系统调用时,首先由Janus的内核模块进行处理,内核模块会与用户态引擎交互,判断请求是否合法。如果合法,则交由内核继续处理;否则,请求将被拒绝。
后两篇论文指出了Janus的一些缺点,包括ptrace机制本身的局限性,以及在系统调用监控和请求拒绝方式上的不足。鉴于篇幅较长,此处不再赘述。
Ostia
Ostia是在Janus等相关论文之后发表的,因此引用了Janus论文中提到的三篇文献。它的最大贡献在于提出了一种全新的架构,解决了此前基于过滤器的架构难以克服的问题。
Ostia与Janus在实现原理上相似,都需要在内核态进行一些修改。然而,两者的区别在于架构设计:Janus需要在内核中有一个负责跟踪的模块,如ptrace,同时在用户态配备一个策略引擎,二者协同工作,其中是否拒绝请求的逻辑由用户态引擎决定,而内核模块则主要负责进程监控。
在我看来,Ostia的实现借鉴了虚拟化的一些思想。当系统调用到达内核时,会回调到调用者用户态内存空间中的一个处理器,再由该处理器转发给代理,最终由代理负责权限和访问的验证。具体的实现细节仍在研究中。
基于软件的故障隔离(Software-based Fault Isolation)
SFI
这篇文章发表于1993年,也是首次提出“sandboxing”一词的文献。文中主要介绍了一种通过软件手段实现隔离的方法。传统的隔离通常在操作系统层面进行,过去进程间可以通过RPC通信,而隔离则依靠虚拟内存来实现。然而,这种方式的开销极大,因此本文提出在同一个内存空间内实现错误隔离。SFI的实现利用了处理器的段寄存器。段寄存器最初是为了应对Intel 8086处理器中数据总线与地址总线宽度不一致的问题而引入的。由于这种不匹配导致寻址无法在单个指令周期内完成,Intel便引入了段寄存器,将整个内存空间划分为四个段,段寄存器存储每个段的前N位地址,通过段内偏移量而非完整的物理地址来描述内存地址,从而解决了这一问题。如今,现代处理器的架构已不再存在地址宽度不统一的问题,因此内存分段已成为可选特性。而SFI则通过对段寄存器访问的限制,将程序的控制流严格限定在一个Fault Domain的代码段内。当进行控制转移时,系统会强制检查段寄存器,若访问地址的前N位与段基地址不符,则会触发异常,表明应用程序正试图逃离其所属的Fault Domain。
Google Native Client
这篇论文是在CSP课程上阅读的,因为需要做分享,所以相比其他论文读得更为仔细。之前阅读时已经写了一些笔记,内容较为冗长。这里仅简要介绍论文的大致思路及个人的一些看法。
Google Native Client(NaCl)简单来说是一种在浏览器中运行原生代码的技术。其技术原型类似于微软臭名昭著的ActiveX。与ActiveX那种毫无安全性的实现不同,NaCl采用了经过改进的基于软件的故障隔离(SFI)技术,并结合了ptrace等系统调用拦截工具,从而实现了在浏览器中安全运行原生代码的功能。从实现角度来看,首先会对代码进行静态检查,确保其符合NaCl制定的一些规则,随后将程序运行在一个沙箱中。原生代码与外界的所有通信,包括系统调用,都会被封装或拦截,以此来实现对原生代码的安全隔离。2009年,Google组织了一场Native Client安全竞赛,鼓励开发者寻找NaCl的漏洞。最终发现了20多个漏洞,但没有一个能够从根本上破坏NaCl的保护机制。目前,Google Chrome浏览器仍然支持以这种方式运行原生代码,不过似乎使用的人并不多。演示非常容易运行,感兴趣的话可以尝试一下,只需简单的步骤即可实现从CPP代码到Javascript代码的通信。
为了提高浏览器中代码运行的效率,还有另一种做法,即asm.js。它的实现思路与NaCl完全不同,asm.js并不在浏览器中执行原生代码,因此无需考虑那么多安全问题,而是通过修改LLVM工具链,将原生代码编译成Javascript的一个子集,然后运行这个子集的Javascript代码。这种方法的性能最高只比原生应用慢一倍,虽然不如NaCl那样接近原生体验,但也可以接受。这正是Firefox浏览器所采用的路线。
目前业界已经形成了相对统一的方案,即WebAssembly。WebAssembly与asm.js出自同一团队,得到了多家公司的支持。WebAssembly由asm.js团队和NaCl团队共同开发,其中NaCl团队更专注于安全方面,这也是他们的专长。因此可以说,Native Client这一功能可能已经被逐渐淘汰,但其安全相关的实现仍由原班人马为WebAssembly贡献力量。实际上,这篇论文的主题并非强调Native Client的实现有多好,而是着重说明它是如何做到安全的。依我看,asm.js的做法更为合理,因为它对系统的侵入性更小。
语言无关的沙箱(Language-Independent Sandboxing)
// TODO 添加笔记
系统(System)
文件系统
RAMCloud
为什么 RAMCloud 在 DRAM 中的数据采用日志结构化策略?
RAMCloud 的备份机制并未完全依赖内存,而是同时在内存和硬盘上各保留一份备份。这种策略虽然节省了内存资源,但也带来了两个问题:
- 备份速度较慢,可能影响系统的正常运行;
- 系统崩溃后,恢复过程需要从硬盘读取数据,耗时较长。
为了解决第一个问题,RAMCloud 采用了日志结构化策略。当请求到达内存时,会以日志形式记录,随后将日志条目分发到各个备份节点。各备份节点接收到条目后立即返回确认,再异步处理这些条目,从而实现了近似异步的操作。这样可以隐藏备份操作带来的开销。然而,这种方法也引入了新的风险:系统崩溃时,缓冲区中的数据可能尚未刷新到持久存储中。为弥补这一不足,文中提出了两种方案来确保数据一旦进入缓冲区即被持久化:一种是使用 DIMM 加超级电容器,另一种则是配备电池。
综上所述,日志结构化的主要目的是解决同步备份过程中因存储层次结构带来的性能瓶颈问题。
RAMCloud 使用何种策略来放置段副本?恢复时如何定位这些段副本?
传统的实现通常依赖中心化的协调器,这会导致性能瓶颈。因此,RAMCloud 采用了去中心化的思想,通过随机化和微调的方式分散备份。其做法类似于 k 选一的过程:首先随机选择若干个候选位置,再从中选出最优的一个,并引入拒绝机制以避免在乐观并发场景下产生竞争。此外,为了尽可能接近最优解,RAMCloud 还会考虑硬盘的访问速度以及当前硬盘上已有的段数量进行微调,从而实现更均匀的分布。
在恢复阶段,传统方法同样依赖中心化的协调器维护一张全局表,这显然会成为性能瓶颈。为此,RAMCloud 在恢复时会向所有备份节点查询,每个备份节点会返回其所存储的副本列表。整个查询过程是并行的,且 RAMCloud 使用了自认为高效的 RPC 通信方式,因此恢复过程并不会特别缓慢。
RAMCloud 是否支持随机访问?如果支持,请说明其实现方式。
支持。RAMCloud 在每个主节点上维护了一个哈希表,其结构为 <表标识符, 对象标识符>,通过该哈希表可以实现高效的随机访问。
乐观崩溃一致性
这篇论文发表于 SOSP'13,主要工作是在基于日志的 Ext4 文件系统基础上,提出了一种乐观崩溃恢复方法。该方法能够在保证崩溃一致性的同时大幅提升性能。然而,计算机领域的任何性能提升都伴随着权衡:乐观崩溃一致性牺牲了数据的新鲜度。
所谓崩溃一致性,是指文件系统在崩溃后,其内部数据是否仍然保持一致,尤其是元数据与实际数据之间的一致性。如果不一致,通常意味着硬盘丢失了部分数据,或者文件系统无法正确识别硬盘上的数据。
在基于日志的文件系统中,一次写入磁盘的操作涉及四个部分:实际数据、日志中的元数据冗余、日志中的提交块以及最终的元数据更新。这四部分必须按顺序写入才能保证崩溃一致性。而要确保写入顺序,就需要借助磁盘的刷新操作,将数据从缓存强制写入物理磁盘。然而,这种强制刷新操作会显著降低性能。本文提出使用校验和、异步持久化通知和延迟写等技术,使文件系统无需强制刷新即可完成写入操作。不过,这种优化会牺牲数据的新鲜度:在 Ext4 中,崩溃后最多可能丢失一个事务的数据,而在本文提出的方案中,可能会丢失多个事务的数据。尽管如此,该方法的性能相比需要刷新的 Ext4 提升了 4 到 10 倍。
F2FS
随着 NAND 闪存技术的发展,越来越多的持久化存储设备开始转向 SSD。然而,传统的文件系统大多针对 HDD 设计,未能充分利用闪存存储的硬件特性。本文提出了一种专为闪存设计的文件系统,目前已集成到 Linux 内核中。整篇论文内容偏工程化,阅读起来并不像其他文件系统论文那样晦涩。
众所周知,闪存存储的读取速度非常快,且其工作原理与 HDD 不同——它并非依靠机械结构和磁头进行读写,而是基于电子方式,因此具有一定的并行性。与此同时,闪存存储的写入操作并不是原地修改,而是需要先写入新位置,再更新指针指向新位置。这一特性导致了“游走树”问题,这是日志结构化文件系统在 SSD 上常见的问题:由于闪存的写入是非原地操作,每次写入都会改变数据块的地址,从而需要递归地更新直接块、间接块等一系列数据结构。为了解决这个问题,F2FS 引入了一个新的表:节点地址表(NAT)。间接块指向 NAT,这样一来,当某个数据块被修改时,只需更新直接块和 NAT 中的条目,即可避免传播到间接块中。
此外,为了充分利用闪存的并行性,F2FS 采用了多头日志机制,不再只使用一个日志,而是根据数据更新频率创建多个日志,从而提升了性能。
PMFS
目前出现了一种新型概念——非易失性内存(NVM),它是通过在内存中加入电容等方式实现持久性的技术。许多研究也在这一基础上设计文件系统,本文正是这样一种文件系统。
惭愧的是,我对这篇文章的理解较为浅显,但可以简要介绍其大致实现。最令人印象深刻的是,该文件系统在保证一致性时采用了混合方式,结合了 COW、日志等多种技术。此外,它还引入了一种新的硬件原语,但由于理解不够深入,此处不便详述。
操作系统
Exokernel
// TODO 添加笔记
内存
事务性内存
事务性内存并不是一个新概念,我从大二起就经常听到这个词,但一直未深入了解相关内容。这篇论文发表于 1993 年,是比较早期的关于硬件支持事务性内存的研究之一。如今,Intel Haswell 架构的 CPU 已经支持事务性内存,但相关应用仍相对较少。
// 临界区
x=VALIDATE;
if (x) {
COMMIT;
} else {
ABORT;
}
上述代码展示了事务性内存的一种编程范式:原本需要使用锁来实现同步的操作,现在都可以被视为一个事务,只有在事务提交时才会生效;若发生冲突,则事务会被中止,所有修改将被丢弃。
本文基于嗅探型缓存一致性协议进行了改进,在硬件层面增加了一个名为 Tx Cache 的 L1 缓存。非事务性操作走常规缓存,而事务性操作则走 Tx Cache。
事务缓存有四种状态:Normal、XCommit、XAbort 和 Empty。事务提交时,XCommit 变为 Empty,XAbort 变为 Normal;事务中止时,XAbort 变为 Empty,XCommit 变为 Normal。
实际上,该实现利用了现有的缓存一致性协议来进行冲突检测。要理解这篇论文,必须对缓存一致性有较为清晰的认识。由于实现依赖于缓存,因此在发生上下文切换等需要刷新缓存的情况下,事务必然会被中止。因此,该实现主要适用于短临界区场景,且不涉及 I/O 操作。
不过,由于实现完全基于现有技术(缓存一致性),它可以与非事务性内存的同步操作并行执行,因为其他非事务性的操作同样会触发缓存操作。这也是该实现的一大优势,非常重要。
CFI
控制流完整性
// TODO 添加笔记
尾部延迟
规模下的尾部延迟
为什么延迟的变异性会随规模扩大而加剧?
这主要是由系统的特性决定的。文中举例说明:假设用户访问一台服务器,99% 的请求能在 10 毫秒内完成,而 1% 的请求则需要 1 秒。在这种情况下,单台服务器的延迟尚可接受。然而,如今大多数应用都需要多个服务协同工作,最终才将结果返回给用户。因此,用户感知到的延迟实际上是所有参与服务中延迟最长的那个值。由此可见,用户感知的延迟会随着系统规模的增大而升高。这里的“规模”指的是系统的复杂程度。
请简要概述一些有效的尾部延迟容忍技术。
例如,最简单的方法是使用多台服务器,每台服务器处理完全相同的逻辑,通过副本机制来降低延迟。用户的请求会被发送到所有服务器,任意一台服务器完成处理后即返回结果。这种方式要求应用程序能够处理幂等请求,同时也使得逻辑变得更加复杂。在此基础上还可以进一步优化,比如在某台服务器完成请求后,通知其他服务器停止处理相同的幂等请求等,但这无疑会增加实现难度。
此外,还可以通过减少守护进程的方式来降低延迟,例如使用精简的内核如 unikernel 等,但这种方法的成本更高。
为什么写操作更容易容忍延迟的变异性?
这是因为写操作通常是异步的,且可以容忍一定程度的不一致。此外,在保证一致性的写操作中,通常会使用 Paxos、ZAB、Raft 等算法,而这些算法本身就具备尾部延迟容忍特性。
锁
非可扩展的锁是危险的
非可扩展的锁是危险的,西拉斯·博伊德-维基泽、M·弗兰斯·卡什霍克、罗伯特·莫里斯和尼古拉伊·泽尔多维奇,《Linux研讨会论文集》。2012年。
为什么票锁的性能会在核心数较少时急剧下降?(提示:缓存一致性协议)
在票锁的实现中,维护着两个票变量:当前票和下一个票。在解锁操作中,会将当前票自增,从而使拿到下一个票的核心获得锁。
凡是遵循文章中提出的硬件缓存一致性模型的系统,都会遇到票锁的快速崩溃问题。文章提出的硬件缓存一致性模型用目录的方式类比了CPU中的缓存,并且用马尔可夫链模型对票锁的问题进行了分析。其中每一个节点代表有几个核心处于等待状态,到达率和服务率分别代表了在不同状态下锁的获取和释放。其中到达率与未处于等待状态的核心数量(n - k)成正比,服务率则与处于等待状态的核心数量(k)成反比。因此,随着k的增大,服务率会逐渐降低。这使得模型得出了一个数学结论:锁的获取时间与正在等待锁的核心数量成正比。
因此,随着核心数量的增加,在串行部分很小时,Sk = 1/(s + ck/2) 中k对Sk的贡献会越来越大,从而更容易受到k的影响。这就是为什么在串行部分很小的时候,仅仅多出几个等待锁的核心,就会导致性能的雪崩。
为什么MCS锁比票锁具有更好的可扩展性?
在票锁中,所有核心都依赖同一个变量来获取锁;而在MCS锁中,前一个核心在释放锁时会修改下一个核心的locked变量,通知下一个核心来获取锁。这样每个锁都只依赖于属于自己的一个变量,这种实现方式与缓存一致性无关,因此具有更好的可扩展性。
Bug
STACK
未定义行为是指编程语言规范对某段代码可能产生的某些执行结果未予定义。不稳定代码则是指在程序实际执行过程中,由于涉及未定义行为,从而无法被编译器翻译(直接略过)的代码段。
STACK会在假设Δ被允许和不允许的情况下分别模拟编译。
- 先模拟假设不成立的情况进行一次编译;
- 模拟假设成立的情况进行一次编译;
- 查看前两步的执行结果有没有区别,有区别的地方就是不稳定代码。
如果执行第二步时得不到准确的结果,那么会漏报一些不稳定代码;如果执行第一步时得不到准确的结果,就会产生误报(false warning / false positive)。目前STACK给出的未定义行为模式可能并不齐全。
对于程序员来说,可以通过修复bug或者去掉一些会被编译器视为未定义行为的代码;对于编译器来说,可以集成一些现有的bug-finding工具,或者利用STACK的方式来判定不稳定代码;同时完善编程语言的specification,定义更多的代码执行规则,减少未定义行为的产生。
STACK为了使可扩展性更高,在计算Δ = ∀e:Reach(e) → ¬Undef(e)的时候做了一些近似运算,使最后得到的结果可能会漏掉一些不稳定代码。STACK为了简化和滤过某些查询用到的constraint solver如果发生了timeout,也会出现漏报的情况。因此,STACK为了更好的扩展性,牺牲了一定的可靠性(精度)。
安全(Security)
虚拟机安全(Virtulization, Security)
CloudVisor
CloudVisor:通过嵌套虚拟化为多租户云中的虚拟机提供保护
// TODO 添加注释
Taint Tracing
TaintDroid
Taint分析是指将一些敏感数据标注出来,在程序执行的过程中确保这些被标注的敏感数据不会被泄露出去的技术。TaintDroid是一个在Android上进行Taint分析的工具。之前的Taint分析工具开销非常大,而TaintDroid通过分层的思想,在不同层做不同粒度的Taint跟踪,大大降低了运行时的开销。
论文有一个配套的demo,是可以运行的,感兴趣的话可以自己试试看,这里也有一个Demo视频。很有趣的是这篇论文是Intel Labs参与的,不是很懂他们怎么会想到做这样的事情。
ROP
Hacking Blind
这篇论文看上去就很酷,实现很让人亮眼。最简单的ROP,就是寻找一个个的gadget,然后把gadget连接起来。然后让控制流走到这些gadget里,就OK了。但是这篇论文是如何在远程来劫持控制流,来实现ROP攻击。攻击者不了解远程的系统,因此首先系统要有一个已知的stack overflow漏洞,然后要求攻击的进程在死了后会重启,而且ASLR后的地址不变。
其实条件是很苛刻的,而且也不懂为什么一个攻击者可以在不了解远程系统的同时知道系统的stack overflow漏洞。整体攻击的过程,是先Dump服务器的内存,然后再进行常规的ROP,其中Dump内存的操作非常精巧,感觉只有ROP高级玩家才能想出这样的做法,具体可以看看上面链接的论文,是我们学院IPADS实验室的一个学长写的,很清楚。
大数据
框架
Hadoop
// TODO 等待阅读
Spark
// TODO 等待阅读
数据处理
Facebook的实时数据处理
网络
网络功能虚拟化(NFV)
Click
Click模块化路由器,罗伯特·莫里斯、埃迪·科勒、约翰·詹诺蒂、M·弗兰斯·卡什霍克,SOSP 1999
为什么Click必须同时提供push'和pull'两种方法?我们能否消除其中一种操作?如何/为什么?
Push和Pull是两种连接方式。Push是从源Element到下游的元素。在Push连接方式里,上游的元素会提交一个包给下游的元素。Pull是从目的地元素到上游元素。在Pull的连接方式里,是下游的元素发送包请求到上游的元素。Push和Pull的共同使用可以使包转发连接的适当终止,可以很好地解决路由器控制流问题。例如包调度的决定——选择哪个队列去请求一个包对于组合的Pull元素来说是非常容易实现的。另外,系统不应该向繁忙的转发接口发送包,否则,这个接口就必须存储包,并且路由器会失去处理这些包的能力(丢弃,修改优先级等)。这个约束可以由简单的给转发接口一个Pull输入实现。然后这个接口就可以控制包转发,并且可以在它准备好的时候请求包。
Click的一个局限性是难以在pull和push路径之间调度CPU时间。为什么这么困难?你会如何改进它?
Click的调度器就是一个Pull Element,它有多个输出,而只有一个输入。至于CPU时间调度的问题,就是说Click没办法处理多个设备同时接收或发送数据的情况。目前的处理方法是linux处理大部分这样的调度,剩下的交由Click来做。最终所有这些都应该由一个单一的机制来控制。关于改进,可以将linux里相关的逻辑作为一个Element引入,不知是否可行。
如何将批处理应用到Click中?请具体说明,并考虑对延迟和吞吐量的影响。
APSys 2012上有一篇论文《Click模块化路由器中的批处理力量》,它尝试了psio、netmap、PF_ring等开源的IO batching工具,最后选择了psio。同时为了实现计算的批处理,它修改了现有的Click。
这样做提高了吞吐量,但增加了延迟。这也跟批处理的程度有关,理论上来说是可以控制的。
常见问题
相似工具推荐
ML-For-Beginners
ML-For-Beginners 是由微软推出的一套系统化机器学习入门课程,旨在帮助零基础用户轻松掌握经典机器学习知识。这套课程将学习路径规划为 12 周,包含 26 节精炼课程和 52 道配套测验,内容涵盖从基础概念到实际应用的完整流程,有效解决了初学者面对庞大知识体系时无从下手、缺乏结构化指导的痛点。 无论是希望转型的开发者、需要补充算法背景的研究人员,还是对人工智能充满好奇的普通爱好者,都能从中受益。课程不仅提供了清晰的理论讲解,还强调动手实践,让用户在循序渐进中建立扎实的技能基础。其独特的亮点在于强大的多语言支持,通过自动化机制提供了包括简体中文在内的 50 多种语言版本,极大地降低了全球不同背景用户的学习门槛。此外,项目采用开源协作模式,社区活跃且内容持续更新,确保学习者能获取前沿且准确的技术资讯。如果你正寻找一条清晰、友好且专业的机器学习入门之路,ML-For-Beginners 将是理想的起点。
funNLP
funNLP 是一个专为中文自然语言处理(NLP)打造的超级资源库,被誉为"NLP 民工的乐园”。它并非单一的软件工具,而是一个汇集了海量开源项目、数据集、预训练模型和实用代码的综合性平台。 面对中文 NLP 领域资源分散、入门门槛高以及特定场景数据匮乏的痛点,funNLP 提供了“一站式”解决方案。这里不仅涵盖了分词、命名实体识别、情感分析、文本摘要等基础任务的标准工具,还独特地收录了丰富的垂直领域资源,如法律、医疗、金融行业的专用词库与数据集,甚至包含古诗词生成、歌词创作等趣味应用。其核心亮点在于极高的全面性与实用性,从基础的字典词典到前沿的 BERT、GPT-2 模型代码,再到高质量的标注数据和竞赛方案,应有尽有。 无论是刚刚踏入 NLP 领域的学生、需要快速验证想法的算法工程师,还是从事人工智能研究的学者,都能在这里找到急需的“武器弹药”。对于开发者而言,它能大幅减少寻找数据和复现模型的时间;对于研究者,它提供了丰富的基准测试资源和前沿技术参考。funNLP 以开放共享的精神,极大地降低了中文自然语言处理的开发与研究成本,是中文 AI 社区不可或缺的宝藏仓库。
cs-video-courses
cs-video-courses 是一个精心整理的计算机科学视频课程清单,旨在为自学者提供系统化的学习路径。它汇集了全球知名高校(如加州大学伯克利分校、新南威尔士大学等)的完整课程录像,涵盖从编程基础、数据结构与算法,到操作系统、分布式系统、数据库等核心领域,并深入延伸至人工智能、机器学习、量子计算及区块链等前沿方向。 面对网络上零散且质量参差不齐的教学资源,cs-video-courses 解决了学习者难以找到成体系、高难度大学级别课程的痛点。该项目严格筛选内容,仅收录真正的大学层级课程,排除了碎片化的简短教程或商业广告,确保用户能接触到严谨的学术内容。 这份清单特别适合希望夯实计算机基础的开发者、需要补充特定领域知识的研究人员,以及渴望像在校生一样系统学习计算机科学的自学者。其独特的技术亮点在于分类极其详尽,不仅包含传统的软件工程与网络安全,还细分了生成式 AI、大语言模型、计算生物学等新兴学科,并直接链接至官方视频播放列表,让用户能一站式获取高质量的教育资源,免费享受世界顶尖大学的课堂体验。
ragflow
RAGFlow 是一款领先的开源检索增强生成(RAG)引擎,旨在为大语言模型构建更精准、可靠的上下文层。它巧妙地将前沿的 RAG 技术与智能体(Agent)能力相结合,不仅支持从各类文档中高效提取知识,还能让模型基于这些知识进行逻辑推理和任务执行。 在大模型应用中,幻觉问题和知识滞后是常见痛点。RAGFlow 通过深度解析复杂文档结构(如表格、图表及混合排版),显著提升了信息检索的准确度,从而有效减少模型“胡编乱造”的现象,确保回答既有据可依又具备时效性。其内置的智能体机制更进一步,使系统不仅能回答问题,还能自主规划步骤解决复杂问题。 这款工具特别适合开发者、企业技术团队以及 AI 研究人员使用。无论是希望快速搭建私有知识库问答系统,还是致力于探索大模型在垂直领域落地的创新者,都能从中受益。RAGFlow 提供了可视化的工作流编排界面和灵活的 API 接口,既降低了非算法背景用户的上手门槛,也满足了专业开发者对系统深度定制的需求。作为基于 Apache 2.0 协议开源的项目,它正成为连接通用大模型与行业专有知识之间的重要桥梁。
PaddleOCR
PaddleOCR 是一款基于百度飞桨框架开发的高性能开源光学字符识别工具包。它的核心能力是将图片、PDF 等文档中的文字提取出来,转换成计算机可读取的结构化数据,让机器真正“看懂”图文内容。 面对海量纸质或电子文档,PaddleOCR 解决了人工录入效率低、数字化成本高的问题。尤其在人工智能领域,它扮演着连接图像与大型语言模型(LLM)的桥梁角色,能将视觉信息直接转化为文本输入,助力智能问答、文档分析等应用场景落地。 PaddleOCR 适合开发者、算法研究人员以及有文档自动化需求的普通用户。其技术优势十分明显:不仅支持全球 100 多种语言的识别,还能在 Windows、Linux、macOS 等多个系统上运行,并灵活适配 CPU、GPU、NPU 等各类硬件。作为一个轻量级且社区活跃的开源项目,PaddleOCR 既能满足快速集成的需求,也能支撑前沿的视觉语言研究,是处理文字识别任务的理想选择。
awesome-machine-learning
awesome-machine-learning 是一份精心整理的机器学习资源清单,汇集了全球优秀的机器学习框架、库和软件工具。面对机器学习领域技术迭代快、资源分散且难以甄选的痛点,这份清单按编程语言(如 Python、C++、Go 等)和应用场景(如计算机视觉、自然语言处理、深度学习等)进行了系统化分类,帮助使用者快速定位高质量项目。 它特别适合开发者、数据科学家及研究人员使用。无论是初学者寻找入门库,还是资深工程师对比不同语言的技术选型,都能从中获得极具价值的参考。此外,清单还延伸提供了免费书籍、在线课程、行业会议、技术博客及线下聚会等丰富资源,构建了从学习到实践的全链路支持体系。 其独特亮点在于严格的维护标准:明确标记已停止维护或长期未更新的项目,确保推荐内容的时效性与可靠性。作为机器学习领域的“导航图”,awesome-machine-learning 以开源协作的方式持续更新,旨在降低技术探索门槛,让每一位从业者都能高效地站在巨人的肩膀上创新。