草庐IT

Linux 下多线程和多进程程序的优缺点,各自适合什么样的业务场景?

Gibson314 2023-03-28 原文

简单说,对于需要资源隔离的场景,多进程能解决,但多线程无法解决,在这里,讲一个我们的小故事,先说下背景:

我是 Terark 和 Topling 的创始人,ToplingDB(兼容 RocksDB)是我们的核心产品。

ToplingDB 的一个重要功能是 分布式 Compact,去年我们实现了 托管 Todis 的 分布式 Compact 支持。最近我们正在实现 MySQL 的 分布式 Compact,我们通过 Facebook 的 MyRocks 来实现 基于 ToplingDB 的 MySQL,内部名称 MyTopling。

在我们的分布式 Compact 架构 上,MySQL 实例与 Compact Worker(后面简称 Worker)是多对多的,用白话说就是同一个 Worker 可以服务来自多个不同的 MySQL 实例的 Compact 任务,同一个 MySQL 实例的多个 Compact 任务也会由多个不同的 Worker 执行。


这个架构在 MySQL 实例这边没啥问题,但是在 Worker 端就有问题了,因为 Worker 是多线程架构,其实这个架构本来没啥问题,在 Todis 中,它就工作得很好。真正问题在于:MyRocks 使用了大量的全局变量(全局数据字典、统计信息等等),而我们的分布式 Compact 直接复用了 MyRocks 的代码,这样就导致,当同一个 Worker 服务不同 MyRocks 实例的 Compact 任务时,全局变量就会冲突混淆

要解决这个问题,我们要么修改 MyRocks 代码,把全局变量都消除掉,要么在架构上支持 MyRocks 这样 不太规范 的应用。我们选择了后者,这就需要将多线程模型改成多进程模型。为了最小化工作量,我们使用了“微创手术”修改法,主要包括以下修改:

在 Worker 程序中,把执行 Compact 的代码,包在新 fork 出来的子进程中

实现中碰到一个小问题,子进程在执行 ::exit 时,陷入了死锁,最后直接通过 ::_exit 解决掉

关于 ::exit 和 ::_exit,备战面试的同学们需要仔细了解下

在 ToplingZipTable 中,分离出一个 ZipServer 进程,执行 Topling 压缩算法

在 Topling PA-Zip 算法中,数据的压缩在一个多线程 Pipeline 中执行,并且通过一些技巧来最大化 CPU L2/L3 的利用率,跟不使用 Pipeline 相比,性能提高了 40% 以上

在多线程架构中,Compact 线程将每条原始 Value 提交到 Pipeline 的队列

在多进程架构中,Compact 线程将保存了所有原始 Value 的文件(通过本地 Http 调用)提交给 ZipServer 进程,在 ZipServer 进程内部,执行原先多线程架构中执行的压缩计算,压缩结束后 Http 调用返回给客户端(Compact 线程)

在具体实现上,使用本地 127.0.0.1 http 服务,通过 json 传递元数据,通过文件传递大块数据,中等长度和较短的二进制数据,通过 process_vm_readv/writev 来传递。

为了最小化对外依赖,http server 我们继续使用(内嵌的)civetweb,http client 使用 libcurl,期间遭遇了一个 libcurl 的 Expect: 100-continue 问题,这充分说明,即便是象 libcurl 这样千锤百炼的基础组件,也得防范它们的“天坑”

最后,我们得到了一个惊喜:在 MySQL 的分布式 Compact 开发完成之前,我们使用 Todis 来测试多进程模型(使用 wikipedia 数据),发现 Worker 的吞吐率竟然提升了 30%,在双路至强 E5-2682(共32核64线程)上,单 Worker 结点的 Compact 吞吐率达到 900MB/sec,而多线程模型中吞吐率不到 700MB/sec。我们猜测,可能是因为多进程中锁的冲突降低,同时因为资源隔离,操作系统也可以进行更好的调度!

有关Linux 下多线程和多进程程序的优缺点,各自适合什么样的业务场景?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  5. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  6. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  7. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  8. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

  9. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  10. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

随机推荐