CAP 是 Consistency、Availablity、Partition-tolerance 的缩写,由计算机科学家埃里克·布鲁尔在 2000 年提出的,所以又称布鲁尔定理 (Brewer’s theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点
Consistency(一致性):如果对任意一个节点的数据就行修改成功后,所有其他节点都能读取到最新的值,那么这个系统就被认为具有严格的一致性。
Availability(可用性):每次请求都能获取到非错的响应,即单节点宕机可从其他节点获取到响应,但是不能保障获取到的数据为最新的数据,即和一致性互斥
Partition tolerance(分区容错性):当节点间出现网络分区(不同节点处于不同的子网络,子网络之间是联通的,但是子网络之间是无法联通的,也就是被切分成了孤立的集群网络),照样可以提供满足一致性和可用性的服务,除非整个网络环境都发生了故障。
任何一个分布式系统只能满足三选二,即只能 AP 或 CP,必须要有 P 。

CAP 认为分布式环境下网络的故障是常态,比如我们多机房部署下机房间就可能发生光缆被挖断、专线故障等网络分区情况(导致部分节点无法通信,原本一个大集群变成多个独立的小集群),也可能出现网络波动、丢包、节点宕机等,所以分布式系统设计要考虑的是在满足 P 的前提下选择 C 还是 A。
抛开严谨的学术证明我们设想工作中的例子:我们要开发一个分布式缓存服务,只提供简单的读取与写入功能,服务支持多个节点做数据冗余及负载,请求由网关随机分发到其中一个节点,我们必须确保其中一个或几个节点故障时另一些节点仍然可以提供服务,在网络分区形成独立小集群时也可以提供服务,这就必须满足分区容错性(P),我们假设部署了两个服务节点,那么:
如果要保证一致性(C),即所有节点可查询到的数据随时随刻都是一致的(同步中的数据不可查询),就要求一个节点写入数据后必须再将数据写入到另一个节点后才能返回成功,这样当我们读取之前写入的数据时才能确保一致,但上文说明过网络异常在所难免,如果两个服务节点无法相互通讯时为保证一致性在数据写入发现无法同步到另一节点时就会返回错误进而牺牲了可用性(A)。
如果要保证可用性(A),即只要不是服务宕机所有请求都可得到正确的响应,那么在网络异常节点不能通讯的情况下要让数据没有同步到另一节点的请求也返回成功,这就必须牺牲一致性(C)导致在一段时间内(网络异常期间)两个服务节点所查询到的数据可能不同。
所以从中可以简单地发现一致性(C)与可用性(A)是不可能同时满足的。同 FLP Impossibility 一样 CAP 理论也为我们做分布式服务架构指明了方向:分布式系统中我们只能选择 CP(满足一致性牺牲可用性)或 AP(满足可用性牺牲一致性)。
当我们选择 CP,即满足一致性而牺牲可用性时意味着在网络异常出现多个节点孤岛时为了保证各个节点的数据一致系统会停止服务,反之选择 AP,即满足可用性牺牲一致性时网络异常时系统仍可工作,但会出现各节点数据不致的情况。
在我们做微服务架构时需要知道 CAP 并做出架构设计或选型。比如注册中心常用的 Eureka 和 Zookeepr 实现,Eureka 是 AP 的,Zookeeper 是 CP 的,Spring Cloud 之所以推荐 Eureka 是因为它认为注册中心的场景允许出现短暂的数据不一致情况,可用性要高于强一致性,
上面出现了“强一致性”与“弱一致性”两个概念,这其实是对一致性的延展,大量的工程实践的经验表明可用性很重要,一致性也很重要,但可以容许一定的时差,即只要保证在一定时间内达到一致即可,这也就是所谓的最终一致性。要实现强一致性的成本很高,尤其是存在很多数据副本的情况下,区块链的 PoW 及其衍生算法就是典型的代表,它的共识机制是概率强一致性(Probabilistic Strong Consistency),要求等待大多数节点都接受了这笔交易再真正接受它,但是带来的问题是交易的确认严重滞后。
基于此出现了 Base 理论。
BASE 是由 Basically Available(基本可用)、Soft state(软状态)、Eventually consistent(最终一致性)缩写而来的。BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性,让 CAP 三者同时基本实现。
Basically Available:基本可用,就是在某个节点宕机或者发生网络分区的情况,可以让所有请求都强制走主节点,这样保证了数据的一致性可可用性,如果主节点压力比较大可以触发降级熔断机制等,或者限流等,让原先 0.5 秒响应的请求以更长的时间去相应
Soft state:软状态相对原子性来说各个要求都有所降低,原子性(硬状态),要求多个节点的数据副本都是一致的,这是一种"硬状态"。软状态(弱状态)允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延迟
Eventually consistent:最终一致性,一致性也分强一致性和弱一致性,而最终一致性属于弱一致性,就是系统并不保证连续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。但会尽可能保证在某个时间级别(比如秒级别)之后,可以让数据达到一致性状态。
CP 模式,保证一致性
zookeeper 集群是一主多从的模式
zookeeper 集群中的节点有三种角色


步骤:
1、首先集群启动时,会先进行领导者选举,确定哪个节点是 Leader ,哪些节点是 Follower 和 Observer
2、然后 Leader 会和其他节点进行数据同步,采用发送快照和发送 Diff 日志的方式
3、集群在工作过程中,所有的写请求都会交给 Leader 节点来进行处理,从节点只能处理读请求
4、Leader 节点收到一个写请求时,会通过两阶段机制来处理
5、Leader 节点会将该写请求对应的日志发送给其他 Follower 节点,并等待 Follower 节点持久化日志成功
6、Follower 节点收到日志后会进行持久化,如果持久化成功则发送一个 Ack 给 Leader 节点
7、当 Leader 节点收到半数以上的 Ack 后,就会开始提交,先更新 Leader 节点本地的内存数据
8、然后发送 commit 命令给 Follower 节点, Follower 节点收到 commit 命令后就会更新各自本地内存数据
9、同时 Leader 节点还是将当前写请求直接发送给 Observer 节点, Observer 节点收到 Leader 发过来的写请求后直接执行更新本地内存数据
10、最后 Leader 节点返回客户端请求响应成功
结论:通过同步机制和两阶段提交机制来达到集群中节点数据一致
当 zookeeper 集群中的 Leader 宕机后,会触发新的选举,选举期间,整个集群是没法对外提供服务的。直到选出新的 Leader 之后,才能重新提供服务

选举
步骤:
1、Leader 挂了,zookeeper 集群不可用
2、通过选举,Follwoer1 成为了 Leader,zookeeper 集群可用
3、原来的 Leader 启动起来了,变成了集群的 Follwoer5
4、Leader 通过 ZXID 事务 ID 向 Follwoer5 同步数据,Follwoer5 可用
结论:在 zookeeper 选举和同步过程,zookeeper 集群不可用
不管是正常的客户端数据提交流程还是节点宕机后的 Leader 选举和数据同步流程都保证了 zookeeper 集群的一致性,但是在节点宕机后的 Leader 选举和数据同步流程中 zookeeper 集群是不可用的,无法提供可用性,所以 zookeeper 保证了 AP,放弃了 C。
AP 模式保证可用性
eureka 集群中每个节点的角色都一样,都可以提供事物请求和读请求


步骤:
1、Eureka Server 启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过 Replicate 同步注册表,每个 Eureka Server 都存在独立完整的服务注册表信息
2、Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务
3、Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常
4、当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例
5、单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端
6、当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式
7、Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地
8、服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存
9、Eureka Client 获取到目标服务器信息,发起服务调用
10、Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除
Eureka 集群每个节点都相等,都可以提供事物请求和读请求,集群之间定时通过 Replicate 同步注册表并通过心跳检测机制去处理 Client 的上下线,保证了 CP,放弃了 A,这里放弃了一致性,只是说放弃了强一致性,去追求最终一致性
《全面解读 CAP 定理》(https://github.com/Netflix/eureka)
《ZooKeeper:分布式过程协同技术详解》(http://www.17bigdata.com/book/zookeeper/index.html)
作为新的阿里云用户,您可以50免费试用多种优惠,价值高达1,700美元(或8,500美元)。这将让您了解和体验阿里云平台上提供的一系列产品和服务。如果您以个人身份注册免费试用,您将获得价值1,700美元的优惠。但是,如果您是注册公司,您可以选择企业免费试用,提交基本信息通过企业实名注册验证,即可开始价值$8,500的免费试用!本教程介绍了如何设置您的帐户并使用您的免费试用版。关于免费试用在我们开始此试用之前,您还必须遵守以下条款和条件才能访问您的免费试用:只有在一年内创建的账户才有资格获得阿里云免费试用。通过此免费试用优惠,用户可以免费试用免费试用活动页面上列出的每种产品一次。如果您有多个帐
我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:
我似乎经常遇到一些设计问题,但我不知道是什么是真的很合适。一方面我经常听到我应该限制耦合和坚持单一职责,但当我这样做时,我常常发现它很困难到在需要时将信息获取到程序的一部分。为了例如,classSingerdefinitialize(name)@name=nameendattr:nameend那么Song应该是:classSongdefnew(singer)@singer=singerendend或classSongdefnew(singer_name)@singer_name=singer_nameendend后者耦合性小,按道理应该用。但如果我以后发现宋有什么需要了解更多歌手,我的
完成这个有困难。我正在使用seed.rb+factory_girl来使用rakedb:seed填充数据库。(我知道固定装置存在,但我想以这种方式完成,这只是一个示例,数据库将填充复杂的关联对象。)我的种子.rb:require'factory_girl_rails'["QM","CDC","SI","QS"].eachdo|n|FactoryGirl.create(:grau,nome:n)end还有我的/factories/graus.rbFactoryGirl.definedofactory:graudonomeendend但是当我运行时:rakedb:seed我得到:rakeab
我正在使用carrierwave上传视频然后有一个名为thumb的版本,带有自定义处理器,可以获取视频并使用streamio-ffmpeg创建屏幕截图。视频和文件都已正确上传,但在调用uploader.url(:thumb)时我得到:ArgumentError:Versionthumbdoesn'texist!VideoUploader.rbrequire'carrierwave/processing/mime_types'require'streamio-ffmpeg'classVideoUploader5)File.renamethumb_path,current_pathendd
我正在使用Deviseauthtokengem用于验证我的Rails应用程序的某些部分。但是,当我尝试使用注册路径创建新用户时,出现以下错误{"errors":["Authorizedusersonly."]}。这是我用于测试的rspec代码,it'createsauserusingemail/passwordcombo'dopostapi_user_registration_path,{email:'xxx',password:'yyy',password_confirmation:'yyy'}putslast_response.bodyexpect(last_response.bo
这道题开始于here.但随着我对雷神的了解越来越多,情况发生了很大变化。我正在尝试创建一个带参数的Thor::Group子命令。奇怪的是,如果没有参数,它就可以工作。我可以使用Thor::Group作为子命令吗?这在我输入时有效:foocounterfoo/bin/foomoduleFooclassCLI但是当我输入时这不起作用:foocounter5moduleFooclassCLI','Countupfromtheinput.')endclassCounter:numeric,:desc=>"Thenumbertostartcounting"desc"Prints2numbersb
方法调用通常可以省略接收者和参数的括号:deffoo;"foo"endfoo#=>"foo"在上面的例子中,foo在方法调用和对潜在局部变量的引用之间是不明确的。在没有后者的情况下,它被解释为方法调用。但是,当方法名原则上可以是常量名时(即,当它以大写字母开头,并且仅由字母组成时),似乎需要消歧。defFoo;"Foo"endFoo#=>NameError:uninitializedconstantFooFoo()#=>"Foo"self.Foo#=>"Foo"为什么会这样?为什么即使在没有同名常量的情况下,也需要明确区分方法调用和对常量的引用? 最佳答案
我正在使用railswithdevise进行注册。我还添加了一个邀请码,所以不是每个人都可以注册。邀请码通过“/users/sign_up?invite_code=wajdpapojapsd”之类的查询字符串传输,并使用“f.hidden_field:invite_code,:value=>params[”添加到注册表单的隐藏字段中:invite_code]".这很好用。唯一的问题是,如果注册没有得到验证和拒绝,设计重定向到“/users”并丢失带有invite_code的查询字符串。由于电子邮件在尝试失败后保留在注册表单中,我相信这也适用于邀请代码。作为最坏情况的解决方案,在注册
我有一个Rails4.2.3应用程序,我在其中使用Devise进行用户身份验证。我在Bootstrap模式中展示我的注册表单。我已经实现了类似于:https://github.com/plataformatec/devise/wiki/How-To:-Display-a-custom-sign_in-form-anywhere-in-your-app.注册时我不断收到此错误:Completed406NotAcceptablein512033ms(ActiveRecord:5.8ms)ActionController::UnknownFormat(ActionController::Un