分布式系统第二部:一致性vs可用性,一个实际的例子

翻译自LOVE FOR PROGRAMMING


正如我们在这个系列的分布式系统第一部:初探一致性哈希中讨论那样,很难使分布式系统在任何时候都完美的工作。虽然,它具有自我修复能力,但有时我们不得不为了效率和可扩展性来权衡某些重要的特征。我们将在这篇文章中讨论更多相关内容。同样,我们将使用在第一部中开发的分布式系统来解决其他的商业用例。让我们开始吧。

回想一下,你怎么使用一致性哈希开发了能提供负载均衡和容错的系统。你让键值对分布在各节点中来分发负载。为了实现容错,还将键值对复制到一致性哈希环的前面那个节点。到目前为止,这都能很好的运行。你觉得在世界之巅。

你老板对你印象非常深刻。看到你的系统使用一致性哈希能够很好的处理规模增长,他想使用你的分布式系统来实现程序的一个关键部分。你的应用随着移动和桌面客户端普及得到了迅速发展。添加商品到购物车作为业务关键部分,他希望客户使用该功能时有极快的体验。他想要你使用你那个20个节点的集群来保存客户购物车数据。

你当然对此很兴奋,不是吗?但是由于被之前的错误所伤害,你现在更成熟了。有许多问题出现在你的脑海,但现在你问了他三个相关的:

1.我们现在拥有多少用户?他们增长多快?
2.他期望的响应延迟是多少?
3.这个服务的消费者是谁?并且这个服务需要提供什么操作?

[引人深思的事:如果你看一下上面的问题:你在问数据大小、支持的操作和期望响应时间。这是你在编写任何算法前应该问的除了其他一些问题外的主要问题]

他并不太清楚这些问题。他需要时间然后消失了。和平!上面的问题,没有阻止你可视化这个系统的初始设计。你知道你老板想要的是什么。所以你开始思考。为了在第一阶段简化设计,你考虑了你服务可能提供的五个操作:

1.客户可获取购物车商品列表
2.客户可添加一个或多个商品到购物车(如没有购物车,则创建一个)
3.客户可删除购物车中的一个或多个商品
4.客户可更新购物车中的一个或多个商品
5.删除购物车

这些都是购物车比较常规的CRUD操作。由于现在这些数据更加关键,所以你必须多加小心。所以你开始考虑用户体验和相关工作流程。你开始可视化通过客户ID存储购物车细节到各个节点上。当对某个特定客户ID的一个新的购物车操作发送过来时,你将使用你已经被证明的一致性哈希算法来将它导航到相关节点,这个节点保存那个客户的购物车明细,并且保持创建/更新/删除的结果。在这个节点更改并持久化购物车数据之前,它将会要求前面那个节点做同样的更改。当所有的操作完成,则通过服务返回客户端调用请求。所以你的服务的简单用户工作流程看起来如下(在现实中这里可能会有些其他组件,但现在我们保持简单点):
service's simple user workflow

当你在考虑这个问题时,你的老板回来了,并回答了您之前提出的问题。快速回转!!

1.我们现在拥有多少用户?他们增长多快?
[老板]我们现在有1千万用户,并且每个月大致增长1百万。这个数字会随着向前发展越来越好。
2.他期望的响应延迟是多少?
[老板]几秒钟
3.这个服务的消费者是谁?并且这个服务需要提供什么操作?
[老板]消费者可以是内部的也可以是外部的。我们需要支持所有CRUD操作。

坦白的说,关于这些你都已经有了主意,不是吗?你对用户数目做了保守估计。如果你的复制因子为2(1千万×2=2千万记录),如果分布是均匀的,你将会有大概1百万客户记录在各个节点上(包括复制有20个节点)。每个节点将会像现在一样安装10万每个月的速度增加。你看了看数据大小,感觉现在完全能应付,但如果你的应用获得进一步发展,你的数据大小将会大大增加。如果你的数据在某个节点出现负载不平衡,虚拟节点可以帮组你解决它。第二个答案是典型的老板式回答,意思是我们需要尽可能越快越好。

第三个,除了你已经可视化的那些CRUD操作,剩下的是有点耐人寻味。你寻思他的第三个回答。你想公司可能会权衡选择给外部开发者提供API,所以服务由外部使用者。你立即决定你的服务需要以REST方式暴露,所以内部和外部使用者都可以访问它。非常好。想出操作客户购物车资源的URL是比较容易的。如下就是:

基础URL: api/v1/customers/{customer-id}/carts [现在你只有一个购物车关联到一个用户]

GET [获取购物车明细]
api/v1/customers/{customer-id}/carts/1

POST [往购物车添加商品,如果购物车不存在就创建一个
api/v1/customers/{customer-id}/carts/1
{ 新商品进行有效负载 }

DELETE [从购物车删除一个商品]
api/v1/customers/{customer-id}/carts/1/items/{item-id}

UPDATE [更新购物车里的商品]
api/v1/customers/{customer-id}/carts/1
{ 更新商品需要进行有效负载 }

DELETE [删除购物车]
api/v1/customers/{customer-id}/carts/1

[我们会在别的文章中讨论更多REST相关内容。]

回到问题。你只是设计完了REST接口和支持的操作。你很快实现了它。你使用REST实现你的购物车服务。在服务器端,你使用意见存在的组件。在各节点上运行的后台服务收到获得请求并处理它(增删改查),与前面一个节点通讯来更新数据。