复制策略与复制的方式 【已翻译100%】(1/2)

注意: 我写一些短篇的博客已经有些日子了. 我认为是时候改变下了.我不确定这篇博客会带来什么影响,但应该会是很大的影响.请让我知道你觉得更好的方法和理由。
这个问题我已经想了几天了. 我试图想找出通用的复制解决方案能够应用到其他解决方案上去. 如果这个问题解决了,我们就能提供更多的功能组到更多的场景上.

但在之前,我们要谈谈怎么去实现复制, 哪些类型的复制. 我们假设有一个单独的数据库 (没分片,在不同的节点). 普通情况下, 将有下面的选项:

  • 主/从 (Master/slaves)
  • 主/次 (Primary/secondaries)
  • 多重可写组合(Multi write partners)
  • 多重主(Multi master)

上面的是我接下来博客要谈的内容.对于这些内容的目的,他们是完全不想关的。

主/从模式是指这样一种情况,你只有一个主写节点和一个或多个从节点。这一模式的一大特点是你永远无法(至少在正常操作的情况下)对从节点做任何形式的更改。它们纯粹是用来读的,即使冒着损坏数据的风险切断它们与主节点的联系它们也不能变成可写的。

这种方法的一个常见的例子是日志传送。我将在后面详细讨论它,但是你可以看看其他类似系统的文档,将一个从节点变更为可写是一个绝对不凡的经历。得有一个很好的理由。

主/次级模式与主/从模式很类似,但在这种模式里我们可以选择一个次级节点成为主节点。只能有一个主服务器,但好处是允许有一种简单的方法来变更主节点。MongoDB就使用这样一个系统。

多重可写组合系统允许任何节点接受写操作,并且它会留意将变更分发到其他节点。它也需要处理冲突,不像目前提到的其他选择。拥有使两个用户在多个节点同时写入相同值的能力。然而,多重可写组合通常会对同伴节点进行假设。例如,它们会在同步时比较,并有一个单独的协议用来将新的在线节点加入到常规复制组合中。

多重主系统允许、接受并鼓励节点按需添加删除,它们假设会有写冲突,并有在运行的基础上解决冲突的需求。其他节点间没有相互同步的需求,它们通常“重新找主”到一个新的节点并开始复制它,这意味着你需要从一开始就复制所有数据。通常很希望到一个节点挂掉了,希望它在挂掉时已经完成所有变更,然后将它摘除。

让我们看看每一个实际执行的细节,包括一些例子,希望这能让我说得更清楚。

日志泊运(Log Shipping)

主/从通常是通过日志泊运来实现的。理解日志泊运的最简单方法是,主数据库会发送(很神奇,我们真的不太在乎这一点是怎样的)如何直接修改数据库文件的指令给从数据库。换句话说,从概念上讲,它会发送类似以下内容:

1: writep(fd1, 1024, new[]{ 17,85,124,13,86}, 5);
  2: writep(fd1, 18432, new[]{ 12,95,34,83,76,32,59}, 7);b

如你所想,这些是非常底层的修改。这个好处是非常易于捕捉和回放那些改变,而劣势是,你真地不能做任何其它事情。因为改变发生在堆栈的最底部,没有机会去运行任何各类的逻辑。我们只是写入到文件,如同主服务器所做的。

这就是为什么允许从节点写很难的最关键原因。当它产生任何独立的写操作时,它便冒着主节点也在写的风险,这样会产生数据冲突。这就是为什么如果你想切换主从你必需做一系列事情。你必须处理完这些麻烦来保证你不会有两端都写的场景发生。

一旦发生这种情况,你永远不不能再使两端同步了。这发生的几率很低。

增加一个新节点,反过来,这很容易。请务必保留过程,做一个完整的数据库备份并将它移动到另一个节点。然后开始传递日志。因为只有它们在同一个点开始时一切才能安全实施。

请注意,备份的这个版本在处理版本问题上很敏感。你不能在使用最底层存储的版本上做一丁点改变,因为那样可能会丢失一切。这个方法用于生成读复制很好用。事实上,这是大多数情况下使用的方法。

理论上你甚至可以用它来做故障转移,因为如果主节点宕掉了,从节点可以接受写。问题是你如何处理从节点以为主节点宕掉了,而主节点以为一切都正常这种情形。这种情况下你可能让两端都可写,而这将导致不能合并地情况。

理论上讲,因为它们有一个共同的根节点,你或许会觉得有一个引领者,并这样做了,但是这样会导致掉线服务器数据的丢失,或者你会没有可行方法取回的数据。这里我们记录的变化非常小,并且粒度太小以至于不允许你在提取变化信息方面做什么有用工作。

Oplog

这实际上与日志传送方法非常类似,只是不发送底层的文件I/O操作,我们实际上发送的是更高层的命令。这就我们而言有相当多的好处。主服务器可以像如下一样发送日志:

set("users/1", {"name": "oren" });
set("users/2", {"name": "ayende" });
del("users/1");

在次级节点上执行这套指令将导致次级上结果相同的状态。不像日志传送那样,这实际上需要次级服务器进行工作,所以相比应用已经计算过的文件更新这样的代价更昂贵。

然而,这样做的好处是,你可以有一个更可读的日志。它也使把副服务器转为主服务器变得容易很多。最主要的是,如果你不傻的话:它们实际的操作完全是相同的一回事,但因为你是在协议层上工作,而不是文件级,所以你可以得到一些感兴趣的好处。

让我们假设你有相同的“大脑分裂”问题,即主副服务器都认为它自己是主服务器。在用 Log Shipping 的情况下,我们无法调和这个分歧。而在用 Oplog 的情况下,我们却可以做到。这里的关键是,我们可以:

  • 对拒绝操作的服务器之一dump为可恢复状态。
  • 尝试应用两个服务器上的日志,希望它们不是同时工作在同一个文档上。

这就是MongoDB采用的复制模式。并且它采取的处理这种冲突的第一种方法。事实上,这几乎是能够安全解决的唯一选择。当然,当两台服务器上更改相同对象是总是需要手动解决。而且最好是提前预防而不是认为“这样有时奏效”。

你可以在这里看到一些MongoDB如何合并交叉写的讨论。事实上,如果继续使用相同的源数据,你可以在这里看到MongoDB内部的oplog :

1: // Operations
  2:
  3: > use test
  4: switched to db test
  5: > db.foo.insert({x:1})
  6: > db.foo.update({x:1}, {$set : {y:1}})
  7: > db.foo.update({x:2}, {$set : {y:1}}, true)
  8: > db.foo.remove({x:1})
  9:
 10: // Op log view
 11:
 12: > use local
 13: switched to db local
 14: > db.oplog.rs.find()
 15: { "ts" : { "t" : 1286821527000, "i" : 1 }, "h" : NumberLong(0), "op" : "n", "ns" : "", "o" : { "msg" : "initiating set" } }
 16: { "ts" : { "t" : 1286821977000, "i" : 1 }, "h" : NumberLong("1722870850266333201"), "op" : "i", "ns" : "test.foo", "o" : { "_id" : ObjectId("4cb35859007cc1f4f9f7f85d"), "x" : 1 } }
 17: { "ts" : { "t" : 1286821984000, "i" : 1 }, "h" : NumberLong("1633487572904743924"), "op" : "u", "ns" : "test.foo", "o2" : { "_id" : ObjectId("4cb35859007cc1f4f9f7f85d") }, "o" : { "$set" : { "y" : 1 } } }
 18: { "ts" : { "t" : 1286821993000, "i" : 1 }, "h" : NumberLong("5491114356580488109"), "op" : "i", "ns" : "test.foo", "o" : { "_id" : ObjectId("4cb3586928ce78a2245fbd57"), "x" : 2, "y" : 1 } }
 19: { "ts" : { "t" : 1286821996000, "i" : 1 }, "h" : NumberLong("243223472855067144"), "op" : "d", "ns" : "test.foo", "b" : true, "o" : { "_id" : ObjectId("4cb35859007cc1f4f9f7f85d") } }

你可以通过在oplog实体上的命令查看链。例如,上面命令的第7行被变成18行的一个插入。这样似乎也要做很多的工作来避免做任何计算工作,更倾向于用一个简单操作来解决问题。

比如,你有一个看起来像{counter:1}的文档,你在主节点上做了一个类似{$inc:{counter:1}}的更新,结果是{counter:2},而oplog将储存{$set:{counter:2}}。次级节点将这样复制而不是使用$inc。

这是一个非常不错的特性,因为你可以多次操作这样的变更,但是返回的结果是一样的。但是这样会导致一个恶劣的结果,那就是你不能将多次的变更合并处理,当然,你可以采用更好的一些方式来处理这个问题,但是。。我并不喜欢这种方案。

Multi write partners

在这种模式下,我们存在一个服务器的集群,每一个服务器都很类似。所有的写操作都被处理并记录下来。当从源服务器复制到所有的目标服务器的时候,就会问目标服务器:你上次从我这儿操作了多少啦,这里就是从上次到现在的所有的变更哦。在这一点,我们可以从已经复制到所有的目标服务器的日志来看看。

时间: 2024-10-24 08:53:20

复制策略与复制的方式 【已翻译100%】(1/2)的相关文章

复制策略与复制的方式 【已翻译100%】(2/2)

服务器宕机意味着相关的日志变化部分会在尺度上增加,直到同伴节点再次运行起来,或者我们从复制目标中移除这个服务器条目. 到目前为止,这与你要组织 oplog 的方式非常相似.主要的不同是,组织需要记录的真实数据的方式.从 oplog 角度看,你准备向系统中写入发生的变化.并且,对之施行的唯一方式就是以它产生的相同顺序将其应用到 oplog 中.这会导致你只能一直拥有一个单主节点的系统.并且会引发在"大脑分裂"时数据丢失或需要手工合并的场景. 就多重可写组合而言,我们要保持足够的上下文(通

MySQL 5.6 的 GTIDs : 新复制协议和中断复制的新方法 【已翻译100%】

MySQL5.6有很多新的特性,其中很多人都感兴趣的一条就是全局事务序号功能(GTIDs).而大家都对这一特性很感兴趣的原因也很好理解,即:本来重新连接从服务器和一个新的主服务器一直是件很麻烦的事,然而在启用GTIDs功能之后就变得简单易行.可是,GTIDs的使用不单单是用单独的标识符替换旧的二进制日志文件/位置,它也采用了新的复制协议.假如你还不太明白这些,那你可以在这篇文章里学点什么.复制协议:新的 VS 旧的 旧的协议往往简单直接即:首先从服务器上在一个特定的偏移量那里连接到一个给定的二进

AngularJS 提交表单的方式 【已翻译100%】(2/2)

简洁语法 这个例子是以字符串的方式发送数据,并且发送你的头信息.如果你不需要这些,并且希望Angular 的$http POST尽可能的简洁,我们可以使用简写方法: ... $http.post('process.php', $scope.formData) .success(function(data) { ... }); ... 绝对更简洁更容易记住方法. $http 内部控制器: 理想的,你可以将$http请求从controller移除到 service.这只是为了演示目的,我们将会尽快在

AngularJS 提交表单的方式 【已翻译100%】(1/2)

在AngularJS出现之前,很多开发者就面对了表单提交这一问题.由于提交表单的方式繁杂而不同,很容易令人疯掉--然而现在看来,依然会让人疯掉. 今天,我们会看一下过去使用PHP方式提交的表单,现在如何将其转换为使用Angular提交.使用Angular来处理表单,对我而言,是一个"啊哈"时刻(译者:表示了解或发现某事物的喜悦).即使它甚至都没有涉及多少Angular表层的东西,但是它却帮助用户看到表单提交之后的潜力,并且理解两种数据绑定方式. 我们会使用jQuery平台来进行这个处理

Docker —— 用于统一开发和部署的轻量级 Linux 容器 【已翻译100%】

使用Docker容器--轻量灵活的VM同类,来接管"依赖地狱".学习Docker是如何基于LXC技术,通过把应用包装在容器里来使应用具有移植性和独立性. 想象一下可以轻松地把应用和它的依赖打包,然后在其他的开发.测试和生产环境上平滑的运行.这就是开源Docker项目的目标.尽管它现在还没正式到生产阶段,最新的发布(本篇文章编写时是0.7.x)使得Docker实现这一伟大目标又近了一步. Docker容器试图解决"依赖地狱"问题.现代的应用通常从已存在的组件组合而来,

Docker, Java EE 7, 和 Maven with WebLogic 12.1.3 【已翻译100%】

WebLogic 12.1.3已经发布,并且对于JavaEE7的APIs在数据库支持web应用开发上也是最重要的支持.以下是在发行版本中支持的一些标准: Java Persistence API 2.1 (implemented by EclipseLink) JAX-RS 2.0 (implemented by Jersey) JSON-P 1.0 (implemented by GlassFish subproject jsonp) WebSockets 1.0 (implemented b

构建多语言的 WPF 应用 【已翻译100%】(1/2)

下载源代码 - 84.4 KB 导言 在WPF应用程序中搭建多语言支持(Multilingual Support)是我最近在做的一件事,对于不使用英语的人士而言,此举提高了程序的可用性.实现起来要完成以下目标: 一个版本容纳多种语言. 这就意味着不要创建单独的英语版本.法语版本.日语版本等等. 许多电子产品(例如电视和数码相机)在同一模块中支持多语言.你不需要购买不同模块或给软件打补丁来得到与默认设置不同的语言 允许在运行时切换接口语言. 这就是说不需要关闭应用程序并配置操作系统环境,一切都交给

通过 Autostereograms 案例学习 OpenGL 和 OpenCL 的互操作性 【已翻译100%】(1/3)

**引言 ** 在过去的十年里, GPU (图形处理单元)已经从特殊硬件(特供)转变成可以在数值计算领域开辟新篇章的高性能计算机设备. 许多算法可以使用拥有巨大的处理能力的GPU来高速执行和处理大数据量.即使在通常的情况下,不可能将图形硬件编程化, 图形硬件也可以加快算法与图像的处理. 举个例子:通常情况下可以用来计算图形差分,模糊图像, 合并图像,甚至是进行图像(或数组)平均值计算. 随后,可编程方式的出现给编程者带来了极大的便利. 可编程方式所提供的新的可能性,更广泛类别的算法可以移植到GP

编写更好代码的 6 个提示 【已翻译100%】

每周我都可以用四种不同的语言编写至少几百行代码.我也可以同其他与我一同工作的开发者协作进行代码的编辑和审查. 简单来说,有许多代码在到处放着,当它们没有被组织管理起来,但 更重要的是当它们没有写好时,事情就会变得有点复杂起来.让我们来看一看几种能提升我们的代码整体质量的不同方法. 1. 开始构建模块 保持代码一致,可重用且有组织的一个最好方式就是将功能成组的放在一起.例如,别把你所有的js代码都扔到一个main.js文件中,而是要尝试基于功能将它们分组放在分开的文件里面, 然后在你达成你的构建步