mirror of
https://github.com/Vonng/ddia.git
synced 2024-12-06 15:20:12 +08:00
remove consecutive parentheses
This commit is contained in:
parent
076fd93b1c
commit
0b839e08fc
6
ch1.md
6
ch1.md
@ -55,7 +55,7 @@
|
||||
|
||||
* 可靠性(Reliability)
|
||||
|
||||
系统在**困境(adversity)**(硬件故障、软件故障、人为错误)中仍可正常工作(正确完成功能,并能达到期望的性能水准)。请参阅“[可靠性](#可靠性)”。
|
||||
系统在**困境**(adversity,比如硬件故障、软件故障、人为错误)中仍可正常工作(正确完成功能,并能达到期望的性能水准)。请参阅“[可靠性](#可靠性)”。
|
||||
|
||||
* 可伸缩性(Scalability)
|
||||
|
||||
@ -254,7 +254,7 @@
|
||||
|
||||
适应某个级别负载的架构不太可能应付10倍于此的负载。如果你正在开发一个快速增长的服务,那么每次负载发生数量级的增长时,你可能都需要重新考虑架构——或者更频繁。
|
||||
|
||||
人们经常讨论**纵向伸缩(scaling up)**(**垂直伸缩(vertical scaling)**,转向更强大的机器)和**横向伸缩(scaling out)** (**水平伸缩(horizontal scaling)**,将负载分布到多台小机器上)之间的对立。跨多台机器分配负载也称为“**无共享(shared-nothing)**”架构。可以在单台机器上运行的系统通常更简单,但高端机器可能非常贵,所以非常密集的负载通常无法避免地需要横向伸缩。现实世界中的优秀架构需要将这两种方法务实地结合,因为使用几台足够强大的机器可能比使用大量的小型虚拟机更简单也更便宜。
|
||||
人们经常讨论**纵向伸缩**(scaling up,也称为垂直伸缩,即vertical scaling,转向更强大的机器)和**横向伸缩**(scaling out,也称为水平伸缩,即horizontal scaling,将负载分布到多台小机器上)之间的对立。跨多台机器分配负载也称为“**无共享(shared-nothing)**”架构。可以在单台机器上运行的系统通常更简单,但高端机器可能非常贵,所以非常密集的负载通常无法避免地需要横向伸缩。现实世界中的优秀架构需要将这两种方法务实地结合,因为使用几台足够强大的机器可能比使用大量的小型虚拟机更简单也更便宜。
|
||||
|
||||
有些系统是 **弹性(elastic)** 的,这意味着可以在检测到负载增加时自动增加计算资源,而其他系统则是手动伸缩(人工分析容量并决定向系统添加更多的机器)。如果负载**极难预测(highly unpredictable)**,则弹性系统可能很有用,但手动伸缩系统更简单,并且意外操作可能会更少(请参阅“[分区再平衡](ch6.md#分区再平衡)”)。
|
||||
|
||||
@ -354,7 +354,7 @@
|
||||
|
||||
本章探讨了一些关于数据密集型应用的基本思考方式。这些原则将指导我们阅读本书的其余部分,那里将会深入技术细节。
|
||||
|
||||
一个应用必须满足各种需求才称得上有用。有一些**功能需求(functional requirements)**(它应该做什么,比如允许以各种方式存储,检索,搜索和处理数据)以及一些**非功能性需求(nonfunctional )**(通用属性,例如安全性,可靠性,合规性,可伸缩性,兼容性和可维护性)。在本章详细讨论了可靠性,可伸缩性和可维护性。
|
||||
一个应用必须满足各种需求才称得上有用。有一些**功能需求**(functional requirements,即它应该做什么,比如允许以各种方式存储,检索,搜索和处理数据)以及一些**非功能性需求*(nonfunctional,即通用属性,例如安全性、可靠性、合规性、可伸缩性、兼容性和可维护性)。在本章详细讨论了可靠性,可伸缩性和可维护性。
|
||||
|
||||
|
||||
**可靠性(Reliability)** 意味着即使发生故障,系统也能正常工作。故障可能发生在硬件(通常是随机的和不相关的),软件(通常是系统性的Bug,很难处理),和人类(不可避免地时不时出错)。 **容错技术** 可以对终端用户隐藏某些类型的故障。
|
||||
|
4
ch10.md
4
ch10.md
@ -120,7 +120,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
|
||||
|
||||
Ruby脚本在内存中保存了一个URL的哈希表,将每个URL映射到它出现的次数。 Unix管道没有这样的哈希表,而是依赖于对URL列表的排序,在这个URL列表中,同一个URL的只是简单地重复出现。
|
||||
|
||||
哪种方法更好?这取决于你有多少个不同的URL。对于大多数中小型网站,你可能可以为所有不同网址提供一个计数器(假设我们使用1GB内存)。在此例中,作业的**工作集(working set)**(作业需要随机访问的内存大小)仅取决于不同URL的数量:如果日志中只有单个URL,重复出现一百万次,则散列表所需的空间表就只有一个URL加上一个计数器的大小。当工作集足够小时,内存散列表表现良好,甚至在性能较差的笔记本电脑上也可以正常工作。
|
||||
哪种方法更好?这取决于你有多少个不同的URL。对于大多数中小型网站,你可能可以为所有不同网址提供一个计数器(假设我们使用1GB内存)。在此例中,作业的**工作集**(working set,即作业需要随机访问的内存大小)仅取决于不同URL的数量:如果日志中只有单个URL,重复出现一百万次,则散列表所需的空间表就只有一个URL加上一个计数器的大小。当工作集足够小时,内存散列表表现良好,甚至在性能较差的笔记本电脑上也可以正常工作。
|
||||
|
||||
另一方面,如果作业的工作集大于可用内存,则排序方法的优点是可以高效地使用磁盘。这与我们在“[SSTables和LSM树](ch3.md#SSTables和LSM树)”中讨论过的原理是一样的:数据块可以在内存中排序并作为段文件写入磁盘,然后多个排序好的段可以合并为一个更大的排序文件。 归并排序具有在磁盘上运行良好的顺序访问模式。 (请记住,针对顺序I/O进行优化是[第三章](ch3.md)中反复出现的主题,相同的模式在此重现)
|
||||
|
||||
@ -148,7 +148,7 @@ Unix管道的发明者道格·麦克罗伊(Doug McIlroy)在1964年首先描
|
||||
|
||||
如果你希望一个程序的输出成为另一个程序的输入,那意味着这些程序必须使用相同的数据格式 —— 换句话说,一个兼容的接口。如果你希望能够将任何程序的输出连接到任何程序的输入,那意味着所有程序必须使用相同的I/O接口。
|
||||
|
||||
在Unix中,这种接口是一个**文件(file)**(更准确地说,是一个文件描述符)。一个文件只是一串有序的字节序列。因为这是一个非常简单的接口,所以可以使用相同的接口来表示许多不同的东西:文件系统上的真实文件,到另一个进程(Unix套接字,stdin,stdout)的通信通道,设备驱动程序(比如`/dev/audio`或`/dev/lp0`),表示TCP连接的套接字等等。很容易将这些设计视为理所当然的,但实际上能让这些差异巨大的东西共享一个统一的接口是非常厉害的,这使得它们可以很容易地连接在一起[^ii]。
|
||||
在Unix中,这种接口是一个**文件**(file,更准确地说,是一个文件描述符)。一个文件只是一串有序的字节序列。因为这是一个非常简单的接口,所以可以使用相同的接口来表示许多不同的东西:文件系统上的真实文件,到另一个进程(Unix套接字,stdin,stdout)的通信通道,设备驱动程序(比如`/dev/audio`或`/dev/lp0`),表示TCP连接的套接字等等。很容易将这些设计视为理所当然的,但实际上能让这些差异巨大的东西共享一个统一的接口是非常厉害的,这使得它们可以很容易地连接在一起[^ii]。
|
||||
|
||||
[^ii]: 统一接口的另一个例子是URL和HTTP,这是Web的基石。 一个URL标识一个网站上的一个特定的东西(资源),你可以链接到任何其他网站的任何网址。 具有网络浏览器的用户因此可以通过跟随链接在网站之间无缝跳转,即使服务器可能由完全不相关的组织维护。 这个原则现在似乎非常明显,但它却是网络取能取得今天成就的关键。 之前的系统并不是那么统一:例如,在公告板系统(BBS)时代,每个系统都有自己的电话号码和波特率配置。 从一个BBS到另一个BBS的引用必须以电话号码和调制解调器设置的形式;用户将不得不挂断,拨打其他BBS,然后手动找到他们正在寻找的信息。 直接链接到另一个BBS内的一些内容当时是不可能的。
|
||||
|
||||
|
8
ch11.md
8
ch11.md
@ -50,7 +50,7 @@
|
||||
|
||||
在这个**发布/订阅**模式中,不同的系统采取各种各样的方法,并没有针对所有目的的通用答案。为了区分这些系统,问一下这两个问题会特别有帮助:
|
||||
|
||||
1. **如果生产者发送消息的速度比消费者能够处理的速度快会发生什么?** 一般来说,有三种选择:系统可以丢掉消息,将消息放入缓冲队列,或使用**背压(backpressure)**(也称为**流量控制(flow control)**;即阻塞生产者,以免其发送更多的消息)。例如Unix管道和TCP就使用了背压:它们有一个固定大小的小缓冲区,如果填满,发送者会被阻塞,直到接收者从缓冲区中取出数据(请参阅“[网络拥塞和排队](ch8.md#网络拥塞和排队)”)。
|
||||
1. **如果生产者发送消息的速度比消费者能够处理的速度快会发生什么?** 一般来说,有三种选择:系统可以丢掉消息,将消息放入缓冲队列,或使用**背压**(backpressure,也称为**流量控制**,即flow control:阻塞生产者,以免其发送更多的消息)。例如Unix管道和TCP就使用了背压:它们有一个固定大小的小缓冲区,如果填满,发送者会被阻塞,直到接收者从缓冲区中取出数据(请参阅“[网络拥塞和排队](ch8.md#网络拥塞和排队)”)。
|
||||
|
||||
如果消息被缓存在队列中,那么理解队列增长会发生什么是很重要的。当队列装不进内存时系统会崩溃吗?还是将消息写入磁盘?如果是这样,磁盘访问又会如何影响消息传递系统的性能【6】?
|
||||
|
||||
@ -144,7 +144,7 @@
|
||||
|
||||
为了伸缩超出单个磁盘所能提供的更高吞吐量,可以对日志进行**分区**(按[第六章](ch6.md)的定义)。不同的分区可以托管在不同的机器上,使得每个分区都有一份能独立于其他分区进行读写的日志。一个主题可以定义为一组携带相同类型消息的分区。这种方法如[图11-3](img/fig11-3.png)所示。
|
||||
|
||||
在每个分区内,代理为每个消息分配一个单调递增的序列号或**偏移量(offset)**(在[图11-3](img/fig11-3.png)中,框中的数字是消息偏移量)。这种序列号是有意义的,因为分区是仅追加写入的,所以分区内的消息是完全有序的。没有跨不同分区的顺序保证。
|
||||
在每个分区内,代理为每个消息分配一个单调递增的序列号或**偏移量**(offset,在[图11-3](img/fig11-3.png)中,框中的数字是消息偏移量)。这种序列号是有意义的,因为分区是仅追加写入的,所以分区内的消息是完全有序的。没有跨不同分区的顺序保证。
|
||||
|
||||
![](img/fig11-3.png)
|
||||
|
||||
@ -435,7 +435,7 @@ $$
|
||||
|
||||
**复合事件处理(complex event processing, CEP)** 是20世纪90年代为分析事件流而开发出的一种方法,尤其适用于需要搜索某些事件模式的应用【65,66】。与正则表达式允许你在字符串中搜索特定字符模式的方式类似,CEP允许你指定规则以在流中搜索某些事件模式。
|
||||
|
||||
CEP系统通常使用高层次的声明式查询语言,比如SQL,或者图形用户界面,来描述应该检测到的事件模式。这些查询被提交给处理引擎,该引擎消费输入流,并在内部维护一个执行所需匹配的状态机。当发现匹配时,引擎发出一个**复合事件(complex event)**(CEP因此得名),并附有检测到的事件模式详情【67】。
|
||||
CEP系统通常使用高层次的声明式查询语言,比如SQL,或者图形用户界面,来描述应该检测到的事件模式。这些查询被提交给处理引擎,该引擎消费输入流,并在内部维护一个执行所需匹配的状态机。当发现匹配时,引擎发出一个**复合事件**(即complex event,CEP因此得名),并附有检测到的事件模式详情【67】。
|
||||
|
||||
在这些系统中,查询和数据之间的关系与普通数据库相比是颠倒的。通常情况下,数据库会持久存储数据,并将查询视为临时的:当查询进入时,数据库搜索与查询匹配的数据,然后在查询完成时丢掉查询。 CEP引擎反转了角色:查询是长期存储的,来自输入流的事件不断流过它们,搜索匹配事件模式的查询【68】。
|
||||
|
||||
@ -663,7 +663,7 @@ Apache Flink则使用不同的方法,它会定期生成状态的滚动存档
|
||||
|
||||
Storm的Trident基于类似的想法来处理状态【78】。依赖幂等性意味着隐含了一些假设:重启一个失败的任务必须以相同的顺序重播相同的消息(基于日志的消息代理能做这些事),处理必须是确定性的,没有其他节点能同时更新相同的值【98,99】。
|
||||
|
||||
当从一个处理节点故障切换到另一个节点时,可能需要进行**防护(fencing)**(请参阅“[领导者和锁](ch8.md#领导者和锁)”),以防止被假死节点干扰。尽管有这么多注意事项,幂等操作是一种实现**恰好一次语义**的有效方式,仅需很小的额外开销。
|
||||
当从一个处理节点故障切换到另一个节点时,可能需要进行**防护**(fencing,请参阅“[领导者和锁](ch8.md#领导者和锁)”),以防止被假死节点干扰。尽管有这么多注意事项,幂等操作是一种实现**恰好一次语义**的有效方式,仅需很小的额外开销。
|
||||
|
||||
#### 失败后重建状态
|
||||
|
||||
|
8
ch12.md
8
ch12.md
@ -300,7 +300,7 @@ Unix和关系数据库以非常不同的哲学来处理信息管理问题。Unix
|
||||
|
||||
#### 流处理器和服务
|
||||
|
||||
当今流行的应用开发风格涉及将功能分解为一组通过同步网络请求(如REST API)进行通信的**服务(service)**(请参阅“[服务中的数据流:REST与RPC](ch4.md#服务中的数据流:REST与RPC)”)。这种面向服务的架构优于单一庞大应用的优势主要在于:通过松散耦合来提供组织上的可伸缩性:不同的团队可以专职于不同的服务上,从而减少团队之间的协调工作(因为服务可以独立部署和更新)。
|
||||
当今流行的应用开发风格涉及将功能分解为一组通过同步网络请求(如REST API)进行通信的**服务**(service,请参阅“[服务中的数据流:REST与RPC](ch4.md#服务中的数据流:REST与RPC)”)。这种面向服务的架构优于单一庞大应用的优势主要在于:通过松散耦合来提供组织上的可伸缩性:不同的团队可以专职于不同的服务上,从而减少团队之间的协调工作(因为服务可以独立部署和更新)。
|
||||
|
||||
在数据流中组装流算子与微服务方法有很多相似之处【40】。但底层通信机制是有很大区别:数据流采用单向异步消息流,而不是同步的请求/响应式交互。
|
||||
|
||||
@ -438,7 +438,7 @@ MPP数据库的内部查询执行图有着类似的特征(请参阅“[Hadoop
|
||||
|
||||
处理两次是数据损坏的一种形式:为同样的服务向客户收费两次(收费太多)或增长计数器两次(夸大指标)都不是我们想要的。在这种情况下,恰好一次意味着安排计算,使得最终效果与没有发生错误的情况一样,即使操作实际上因为某种错误而重试。我们先前讨论过实现这一目标的几种方法。
|
||||
|
||||
最有效的方法之一是使操作**幂等(idempotent)**(请参阅“[幂等性](ch11.md#幂等性)”);即确保它无论是执行一次还是执行多次都具有相同的效果。但是,将不是天生幂等的操作变为幂等的操作需要一些额外的努力与关注:你可能需要维护一些额外的元数据(例如更新了值的操作ID集合),并在从一个节点故障切换至另一个节点时做好防护(请参阅“[领导者和锁](ch8.md#领导者和锁)”)。
|
||||
最有效的方法之一是使操作**幂等**(idempotent,请参阅“[幂等性](ch11.md#幂等性)”);即确保它无论是执行一次还是执行多次都具有相同的效果。但是,将不是天生幂等的操作变为幂等的操作需要一些额外的努力与关注:你可能需要维护一些额外的元数据(例如更新了值的操作ID集合),并在从一个节点故障切换至另一个节点时做好防护(请参阅“[领导者和锁](ch8.md#领导者和锁)”)。
|
||||
|
||||
#### 抑制重复
|
||||
|
||||
@ -639,13 +639,13 @@ ACID事务通常既提供及时性(例如线性一致性)也提供完整性
|
||||
|
||||
例如,这种系统可以使用多领导者配置运维,跨越多个数据中心,在区域间异步复制。任何一个数据中心都可以持续独立运行,因为不需要同步的跨区域协调。这样的系统的及时性保证会很弱 —— 如果不引入协调它是不可能是线性一致的 —— 但它仍然可以提供有力的完整性保证。
|
||||
|
||||
在这种情况下,可串行化事务作为维护衍生状态的一部分仍然是有用的,但它们只能在小范围内运行,在那里它们工作得很好【8】。异构分布式事务(如XA事务)(请参阅“[实践中的分布式事务](ch9.md#实践中的分布式事务)”)不是必需的。同步协调仍然可以在需要的地方引入(例如在无法恢复的操作之前强制执行严格的约束),但是如果只是应用的一小部分地方需要它,没必要让所有操作都付出协调的代价。【43】。
|
||||
在这种情况下,可串行化事务作为维护衍生状态的一部分仍然是有用的,但它们只能在小范围内运行,在那里它们工作得很好【8】。异构分布式事务(如XA事务,请参阅“[实践中的分布式事务](ch9.md#实践中的分布式事务)”)不是必需的。同步协调仍然可以在需要的地方引入(例如在无法恢复的操作之前强制执行严格的约束),但是如果只是应用的一小部分地方需要它,没必要让所有操作都付出协调的代价。【43】。
|
||||
|
||||
另一种审视协调与约束的角度是:它们减少了由于不一致而必须做出的道歉数量,但也可能会降低系统的性能和可用性,从而可能增加由于宕机中断而需要做出的道歉数量。你不可能将道歉数量减少到零,但可以根据自己的需求寻找最佳平衡点 —— 既不存在太多不一致性,又不存在太多可用性问题。
|
||||
|
||||
### 信任但验证
|
||||
|
||||
我们所有关于正确性,完整性和容错的讨论都基于一些假设,假设某些事情可能会出错,但其他事情不会。我们将这些假设称为我们的**系统模型(system model)**(请参阅“[将系统模型映射到现实世界](ch8.md#将系统模型映射到现实世界)”):例如,我们应该假设进程可能会崩溃,机器可能突然断电,网络可能会任意延迟或丢弃消息。但是我们也可能假设写入磁盘的数据在执行`fsync`后不会丢失,内存中的数据没有损坏,而CPU的乘法指令总是能返回正确的结果。
|
||||
我们所有关于正确性,完整性和容错的讨论都基于一些假设,假设某些事情可能会出错,但其他事情不会。我们将这些假设称为我们的**系统模型**(system model,请参阅“[将系统模型映射到现实世界](ch8.md#将系统模型映射到现实世界)”):例如,我们应该假设进程可能会崩溃,机器可能突然断电,网络可能会任意延迟或丢弃消息。但是我们也可能假设写入磁盘的数据在执行`fsync`后不会丢失,内存中的数据没有损坏,而CPU的乘法指令总是能返回正确的结果。
|
||||
|
||||
这些假设是相当合理的,因为大多数时候它们都是成立的,如果我们不得不经常担心计算机出错,那么基本上寸步难行。在传统上,系统模型采用二元方法处理故障:我们假设有些事情可能会发生,而其他事情**永远**不会发生。实际上,这更像是一个概率问题:有些事情更有可能,其他事情不太可能。问题在于违反我们假设的情况是否经常发生,以至于我们可能在实践中遇到它们。
|
||||
|
||||
|
2
ch2.md
2
ch2.md
@ -513,7 +513,7 @@ db.observations.aggregate([
|
||||
|
||||
但是,要是多对多关系在你的数据中很常见呢?关系模型可以处理多对多关系的简单情况,但是随着数据之间的连接变得更加复杂,将数据建模为图形显得更加自然。
|
||||
|
||||
一个图由两种对象组成:**顶点(vertices)**(也称为**节点(nodes)** 或**实体(entities)**),和**边(edges)**( 也称为**关系(relationships)** 或**弧 (arcs)** )。多种数据可以被建模为一个图形。典型的例子包括:
|
||||
一个图由两种对象组成:**顶点**(vertices,也称为**节点**,即nodes,或**实体**,即entities),和**边**(edges,也称为**关系**,即relationships,或**弧**,即arcs)。多种数据可以被建模为一个图形。典型的例子包括:
|
||||
|
||||
* 社交图谱
|
||||
|
||||
|
2
ch3.md
2
ch3.md
@ -248,7 +248,7 @@ B树的基本底层写操作是用新数据覆写硬盘上的页面,并假定
|
||||
|
||||
为了使数据库能处理异常崩溃的场景,B树实现通常会带有一个额外的硬盘数据结构:**预写式日志**(WAL,即write-ahead log,也称为**重做日志**,即redo log)。这是一个仅追加的文件,每个B树的修改在其能被应用到树本身的页面之前都必须先写入到该文件。当数据库在崩溃后恢复时,这个日志将被用来使B树恢复到一致的状态【5,20】。
|
||||
|
||||
另外还有一个更新页面的复杂情况是,如果多个线程要同时访问B树,则需要仔细的并发控制 —— 否则线程可能会看到树处于不一致的状态。这通常是通过使用**锁存器(latches)**(轻量级锁)保护树的数据结构来完成。日志结构化的方法在这方面更简单,因为它们在后台进行所有的合并,而不会干扰新接收到的查询,并且能够时不时地将旧的段原子交换为新的段。
|
||||
另外还有一个更新页面的复杂情况是,如果多个线程要同时访问B树,则需要仔细的并发控制 —— 否则线程可能会看到树处于不一致的状态。这通常是通过使用**锁存器**(latches,轻量级锁)保护树的数据结构来完成。日志结构化的方法在这方面更简单,因为它们在后台进行所有的合并,而不会干扰新接收到的查询,并且能够时不时地将旧的段原子交换为新的段。
|
||||
|
||||
#### B树的优化
|
||||
|
||||
|
4
ch4.md
4
ch4.md
@ -15,7 +15,7 @@
|
||||
|
||||
在大多数情况下,修改应用程序的功能也意味着需要更改其存储的数据:可能需要使用新的字段或记录类型,或者以新方式展示现有数据。
|
||||
|
||||
我们在[第二章](ch2.md)讨论的数据模型有不同的方法来应对这种变化。关系数据库通常假定数据库中的所有数据都遵循一个模式:尽管可以更改该模式(通过模式迁移,即`ALTER`语句),但是在任何时间点都有且仅有一个正确的模式。相比之下,**读时模式(schema-on-read)**(或**无模式**,即schemaless)数据库不会强制一个模式,因此数据库可以包含在不同时间写入的新老数据格式的混合(请参阅 “[文档模型中的模式灵活性](ch2.md#文档模型中的模式灵活性)” )。
|
||||
我们在[第二章](ch2.md)讨论的数据模型有不同的方法来应对这种变化。关系数据库通常假定数据库中的所有数据都遵循一个模式:尽管可以更改该模式(通过模式迁移,即`ALTER`语句),但是在任何时间点都有且仅有一个正确的模式。相比之下,**读时模式**(schema-on-read,或**无模式**,即schemaless)数据库不会强制一个模式,因此数据库可以包含在不同时间写入的新老数据格式的混合(请参阅 “[文档模型中的模式灵活性](ch2.md#文档模型中的模式灵活性)” )。
|
||||
|
||||
当数据**格式(format)** 或**模式(schema)** 发生变化时,通常需要对应用程序代码进行相应的更改(例如,为记录添加新字段,然后修改程序开始读写该字段)。但在大型应用程序中,代码变更通常不会立即完成:
|
||||
|
||||
@ -66,7 +66,7 @@
|
||||
* 这类编码通常与特定的编程语言深度绑定,其他语言很难读取这种数据。如果以这类编码存储或传输数据,那你就和这门语言绑死在一起了。并且很难将系统与其他组织的系统(可能用的是不同的语言)进行集成。
|
||||
* 为了恢复相同对象类型的数据,解码过程需要**实例化任意类**的能力,这通常是安全问题的一个来源【5】:如果攻击者可以让应用程序解码任意的字节序列,他们就能实例化任意的类,这会允许他们做可怕的事情,如远程执行任意代码【6,7】。
|
||||
* 在这些库中,数据版本控制通常是事后才考虑的。因为它们旨在快速简便地对数据进行编码,所以往往忽略了前向后向兼容性带来的麻烦问题。
|
||||
* 效率(编码或解码所花费的CPU时间,以及编码结构的大小)往往也是事后才考虑的。 例如,Java的内置序列化由于其糟糕的性能和臃肿的编码而臭名昭著【8】。
|
||||
* 效率(编码或解码所花费的CPU时间,以及编码结构的大小)往往也是事后才考虑的。 例如,Java的内置序列化由于其糟糕的性能和臃肿的编码而臭名昭着【8】。
|
||||
|
||||
因此,除非临时使用,采用语言内置编码通常是一个坏主意。
|
||||
|
||||
|
4
ch5.md
4
ch5.md
@ -68,7 +68,7 @@
|
||||
>
|
||||
> 对于异步复制系统而言,主库故障时有可能丢失数据。这可能是一个严重的问题,因此研究人员仍在研究不丢数据但仍能提供良好性能和可用性的复制方法。 例如,**链式复制**【8,9】]是同步复制的一种变体,已经在一些系统(如Microsoft Azure存储【10,11】)中成功实现。
|
||||
>
|
||||
> 复制的一致性与**共识(consensus)**(使几个节点就某个值达成一致)之间有着密切的联系,[第九章](ch9.md)将详细地探讨这一领域的理论。本章主要讨论实践中数据库常用的简单复制形式。
|
||||
> 复制的一致性与**共识**(consensus,使几个节点就某个值达成一致)之间有着密切的联系,[第九章](ch9.md)将详细地探讨这一领域的理论。本章主要讨论实践中数据库常用的简单复制形式。
|
||||
>
|
||||
|
||||
### 设置新从库
|
||||
@ -414,7 +414,7 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
|
||||
>
|
||||
> 已经有一些有趣的研究来自动解决由于数据修改引起的冲突。有几行研究值得一提:
|
||||
>
|
||||
> * **无冲突复制数据类型(Conflict-free replicated datatypes)**(CRDT)【32,38】是可以由多个用户同时编辑的集合,映射,有序列表,计数器等的一系列数据结构,它们以合理的方式自动解决冲突。一些CRDT已经在Riak 2.0中实现【39,40】。
|
||||
> * **无冲突复制数据类型(Conflict-free replicated datatypes,CRDT)**【32,38】是可以由多个用户同时编辑的集合,映射,有序列表,计数器等的一系列数据结构,它们以合理的方式自动解决冲突。一些CRDT已经在Riak 2.0中实现【39,40】。
|
||||
> * **可合并的持久数据结构(Mergeable persistent data structures)**【41】显式跟踪历史记录,类似于Git版本控制系统,并使用三向合并功能(而CRDT使用双向合并)。
|
||||
> * **可执行的转换(operational transformation)**[42]是Etherpad 【30】和Google Docs 【31】等合作编辑应用背后的冲突解决算法。它是专为同时编辑项目的有序列表而设计的,例如构成文本文档的字符列表。
|
||||
>
|
||||
|
6
ch7.md
6
ch7.md
@ -438,7 +438,7 @@ COMMIT;
|
||||
|
||||
#### 比较并设置(CAS)
|
||||
|
||||
在不提供事务的数据库中,有时会发现一种原子操作:**比较并设置(CAS, Compare And Set)**(先前在“[单对象写入](#单对象写入)”中提到)。此操作的目的是为了避免丢失更新:只有当前值从上次读取时一直未改变,才允许更新发生。如果当前值与先前读取的值不匹配,则更新不起作用,且必须重试读取-修改-写入序列。
|
||||
在不提供事务的数据库中,有时会发现一种原子操作:**比较并设置(CAS, 即Compare And Set,先前在“[单对象写入](#单对象写入)”中提到)。此操作的目的是为了避免丢失更新:只有当前值从上次读取时一直未改变,才允许更新发生。如果当前值与先前读取的值不匹配,则更新不起作用,且必须重试读取-修改-写入序列。
|
||||
|
||||
例如,为了防止两个用户同时更新同一个wiki页面,可以尝试类似这样的方式,只有当用户开始编辑页面内容时,才会发生更新:
|
||||
|
||||
@ -593,7 +593,7 @@ COMMIT;
|
||||
|
||||
- 字面意义上地串行顺序执行事务(请参阅“[真的串行执行](#真的串行执行)”)
|
||||
- **两阶段锁定(2PL, two-phase locking)**,几十年来唯一可行的选择(请参阅“[两阶段锁定](#两阶段锁定)”)
|
||||
- 乐观并发控制技术,例如**可串行化快照隔离(serializable snapshot isolation)**(请参阅“[可串行化快照隔离](#可串行化快照隔离)”)
|
||||
- 乐观并发控制技术,例如**可串行化快照隔离**(serializable snapshot isolation,请参阅“[可串行化快照隔离](#可串行化快照隔离)”)
|
||||
|
||||
现在将主要在单节点数据库的背景下讨论这些技术;在[第九章](ch9.md)中,我们将研究如何将它们推广到涉及分布式系统中多个节点的事务。
|
||||
|
||||
@ -763,7 +763,7 @@ WHERE room_id = 123 AND
|
||||
|
||||
相比之下,**串行化快照隔离**是一种**乐观(optimistic)** 的并发控制技术。在这种情况下,乐观意味着,如果存在潜在的危险也不阻止事务,而是继续执行事务,希望一切都会好起来。当一个事务想要提交时,数据库检查是否有什么不好的事情发生(即隔离是否被违反);如果是的话,事务将被中止,并且必须重试。只有可串行化的事务才被允许提交。
|
||||
|
||||
乐观并发控制是一个古老的想法【52】,其优点和缺点已经争论了很长时间【53】。如果存在很多**争用(contention)**(很多事务试图访问相同的对象),则表现不佳,因为这会导致很大一部分事务需要中止。如果系统已经接近最大吞吐量,来自重试事务的额外负载可能会使性能变差。
|
||||
乐观并发控制是一个古老的想法【52】,其优点和缺点已经争论了很长时间【53】。如果存在很多**争用**(contention,即很多事务试图访问相同的对象),则表现不佳,因为这会导致很大一部分事务需要中止。如果系统已经接近最大吞吐量,来自重试事务的额外负载可能会使性能变差。
|
||||
|
||||
但是,如果有足够的备用容量,并且事务之间的争用不是太高,乐观的并发控制技术往往比悲观的要好。可交换的原子操作可以减少争用:例如,如果多个事务同时要增加一个计数器,那么应用增量的顺序(只要计数器不在同一个事务中读取)就无关紧要了,所以并发增量可以全部应用且无需冲突。
|
||||
|
||||
|
16
ch8.md
16
ch8.md
@ -156,7 +156,7 @@
|
||||
|
||||
过早地声明一个节点已经死了是有问题的:如果这个节点实际上是活着的,并且正在执行一些动作(例如,发送一封电子邮件),而另一个节点接管,那么这个动作可能会最终执行两次。我们将在“[知识、真相与谎言](#知识、真相与谎言)”以及[第九章](ch9.md)和[第十一章](ch11.md)中更详细地讨论这个问题。
|
||||
|
||||
当一个节点被宣告死亡时,它的职责需要转移到其他节点,这会给其他节点和网络带来额外的负担。如果系统已经处于高负荷状态,则过早宣告节点死亡会使问题更严重。特别是如果节点实际上没有死亡,只是由于过载导致其响应缓慢;这时将其负载转移到其他节点可能会导致**级联失效(cascading failure)**(在极端情况下,所有节点都宣告对方死亡,所有节点都将停止工作)。
|
||||
当一个节点被宣告死亡时,它的职责需要转移到其他节点,这会给其他节点和网络带来额外的负担。如果系统已经处于高负荷状态,则过早宣告节点死亡会使问题更严重。特别是如果节点实际上没有死亡,只是由于过载导致其响应缓慢;这时将其负载转移到其他节点可能会导致**级联失效**(即cascading failure,表示在极端情况下,所有节点都宣告对方死亡,所有节点都将停止工作)。
|
||||
|
||||
设想一个虚构的系统,其网络可以保证数据包的最大延迟——每个数据包要么在一段时间内传送,要么丢失,但是传递永远不会比$d$更长。此外,假设你可以保证一个非故障节点总是在一段时间内处理一个请求$r$。在这种情况下,你可以保证每个成功的请求在$2d + r$时间内都能收到响应,如果你在此时间内没有收到响应,则知道网络或远程节点不工作。如果这是成立的,$2d + r$ 会是一个合理的超时设置。
|
||||
|
||||
@ -186,7 +186,7 @@
|
||||
|
||||
所有这些因素都会造成网络延迟的变化。当系统接近其最大容量时,排队延迟的变化范围特别大:拥有足够备用容量的系统可以轻松排空队列,而在高利用率的系统中,很快就能积累很长的队列。
|
||||
|
||||
在公共云和多租户数据中心中,资源被许多客户共享:网络链接和交换机,甚至每个机器的网卡和CPU(在虚拟机上运行时)。批处理工作负载(如MapReduce)(请参阅[第十章](ch10.md))能够很容易使网络链接饱和。由于无法控制或了解其他客户对共享资源的使用情况,如果附近的某个人(嘈杂的邻居)正在使用大量资源,则网络延迟可能会发生剧烈变化【28,29】。
|
||||
在公共云和多租户数据中心中,资源被许多客户共享:网络链接和交换机,甚至每个机器的网卡和CPU(在虚拟机上运行时)。批处理工作负载(如MapReduce,请参阅[第十章](ch10.md))能够很容易使网络链接饱和。由于无法控制或了解其他客户对共享资源的使用情况,如果附近的某个人(嘈杂的邻居)正在使用大量资源,则网络延迟可能会发生剧烈变化【28,29】。
|
||||
|
||||
在这种环境下,你只能通过实验方式选择超时:在一段较长的时期内、在多台机器上测量网络往返时间的分布,以确定延迟的预期变化。然后,考虑到应用程序的特性,可以确定**故障检测延迟**与**过早超时风险**之间的适当折衷。
|
||||
|
||||
@ -214,7 +214,7 @@
|
||||
|
||||
如果想通过电路传输文件,你得预测一个带宽分配。如果你猜的太低,传输速度会不必要的太慢,导致网络容量闲置。如果你猜的太高,电路就无法建立(因为如果无法保证其带宽分配,网络不能建立电路)。因此,将电路用于突发数据传输会浪费网络容量,并且使传输不必要地缓慢。相比之下,TCP动态调整数据传输速率以适应可用的网络容量。
|
||||
|
||||
已经有一些尝试去建立同时支持电路交换和分组交换的混合网络,比如ATM[^iii]。InfiniBand有一些相似之处【35】:它在链路层实现了端到端的流量控制,从而减少了在网络中排队的需要,尽管它仍然可能因链路拥塞而受到延迟【36】。通过仔细使用**服务质量(quality of service,)**(QoS,数据包的优先级和调度)和**准入控制(admission control)**(限速发送器),可以在分组网络上模拟电路交换,或提供统计上的**有限延迟**【25,32】。
|
||||
已经有一些尝试去建立同时支持电路交换和分组交换的混合网络,比如ATM[^iii]。InfiniBand有一些相似之处【35】:它在链路层实现了端到端的流量控制,从而减少了在网络中排队的需要,尽管它仍然可能因链路拥塞而受到延迟【36】。通过仔细使用**服务质量**(quality of service,即QoS,数据包的优先级和调度)和**准入控制**(admission control,限速发送器),可以在分组网络上模拟电路交换,或提供统计上的**有限延迟**【25,32】。
|
||||
|
||||
[^iii]: **异步传输模式(Asynchronous Transfer Mode, ATM)** 在20世纪80年代是以太网的竞争对手【32】,但在电话网核心交换机之外并没有得到太多的采用。它与自动柜员机(也称为自动取款机)无关,尽管共用一个缩写词。或许,在一些平行的世界里,互联网是基于像ATM这样的东西,因此它们的互联网视频通话可能比我们的更可靠,因为它们不会遭受包的丢失和延迟。
|
||||
|
||||
@ -250,7 +250,7 @@
|
||||
7. 这个缓存条目何时到期?
|
||||
8. 日志文件中此错误消息的时间戳是什么?
|
||||
|
||||
[例1-4](ch1.md)测量了**持续时间(durations)**(例如,请求发送与响应接收之间的时间间隔),而[例5-8](ch5.md)描述了**时间点(point in time)**(在特定日期,特定时间发生的事件)。
|
||||
[例1-4](ch1.md)测量了**持续时间**(durations,例如,请求发送与响应接收之间的时间间隔),而[例5-8](ch5.md)描述了**时间点**(point in time,在特定日期和和特定时间发生的事件)。
|
||||
|
||||
在分布式系统中,时间是一件棘手的事情,因为通信不是即时的:消息通过网络从一台机器传送到另一台机器需要时间。收到消息的时间总是晚于发送的时间,但是由于网络中的可变延迟,我们不知道晚了多少时间。这个事实导致有时很难确定在涉及多台机器时发生事情的顺序。
|
||||
|
||||
@ -280,13 +280,13 @@
|
||||
|
||||
如果NTP协议检测到计算机的本地石英钟比NTP服务器要更快或更慢,则可以调整单调钟向前走的频率(这称为**偏移(skewing)** 时钟)。默认情况下,NTP允许时钟速率增加或减慢最高至0.05%,但NTP不能使单调时钟向前或向后跳转。单调时钟的分辨率通常相当好:在大多数系统中,它们能在几微秒或更短的时间内测量时间间隔。
|
||||
|
||||
在分布式系统中,使用单调钟测量**经过时间(elapsed time)**(比如超时)通常很好,因为它不假定不同节点的时钟之间存在任何同步,并且对测量的轻微不准确性不敏感。
|
||||
在分布式系统中,使用单调钟测量**经过时间**(elapsed time,比如超时)通常很好,因为它不假定不同节点的时钟之间存在任何同步,并且对测量的轻微不准确性不敏感。
|
||||
|
||||
### 时钟同步与准确性
|
||||
|
||||
单调钟不需要同步,但是日历时钟需要根据NTP服务器或其他外部时间源来设置才能有用。不幸的是,我们获取时钟的方法并不像你所希望的那样可靠或准确——硬件时钟和NTP可能会变幻莫测。举几个例子:
|
||||
|
||||
* 计算机中的石英钟不够精确:它会**漂移(drifts)**(运行速度快于或慢于预期)。时钟漂移取决于机器的温度。 Google假设其服务器时钟漂移为200 ppm(百万分之一)【41】,相当于每30秒与服务器重新同步一次的时钟漂移为6毫秒,或者每天重新同步的时钟漂移为17秒。即使一切工作正常,此漂移也会限制可以达到的最佳准确度。
|
||||
* 计算机中的石英钟不够精确:它会**漂移**(drifts,即运行速度快于或慢于预期)。时钟漂移取决于机器的温度。 Google假设其服务器时钟漂移为200 ppm(百万分之一)【41】,相当于每30秒与服务器重新同步一次的时钟漂移为6毫秒,或者每天重新同步的时钟漂移为17秒。即使一切工作正常,此漂移也会限制可以达到的最佳准确度。
|
||||
* 如果计算机的时钟与NTP服务器的时钟差别太大,可能会拒绝同步,或者本地时钟将被强制重置【37】。任何观察重置前后时间的应用程序都可能会看到时间倒退或突然跳跃。
|
||||
* 如果某个节点被NTP服务器的防火墙意外阻塞,有可能会持续一段时间都没有人会注意到。有证据表明,这在实践中确实发生过。
|
||||
* NTP同步只能和网络延迟一样好,所以当你在拥有可变数据包延迟的拥塞网络上时,NTP同步的准确性会受到限制。一个实验表明,当通过互联网同步时,35毫秒的最小误差是可以实现的,尽管偶尔的网络延迟峰值会导致大约一秒的误差。根据配置,较大的网络延迟会导致NTP客户端完全放弃。
|
||||
@ -327,7 +327,7 @@
|
||||
|
||||
* 数据库写入可能会神秘地消失:具有滞后时钟的节点无法覆盖之前具有快速时钟的节点写入的值,直到节点之间的时钟偏差消逝【54,55】。此方案可能导致一定数量的数据被悄悄丢弃,而未向应用报告任何错误。
|
||||
* LWW无法区分**高频顺序写入**(在[图8-3](img/fig8-3.png)中,客户端B的增量操作**一定**发生在客户端A的写入之后)和**真正并发写入**(写入者意识不到其他写入者)。需要额外的因果关系跟踪机制(例如版本向量),以防止违背因果关系(请参阅“[检测并发写入](ch5.md#检测并发写入)”)。
|
||||
* 两个节点很可能独立地生成具有相同时间戳的写入,特别是在时钟仅具有毫秒分辨率的情况下。为了解决这样的冲突,还需要一个额外的**决胜值(tiebreaker)**(可以简单地是一个大随机数),但这种方法也可能会导致违背因果关系【53】。
|
||||
* 两个节点很可能独立地生成具有相同时间戳的写入,特别是在时钟仅具有毫秒分辨率的情况下。为了解决这样的冲突,还需要一个额外的**决胜值**(tiebreaker,可以简单地是一个大随机数),但这种方法也可能会导致违背因果关系【53】。
|
||||
|
||||
因此,尽管通过保留最“最近”的值并放弃其他值来解决冲突是很诱惑人的,但是要注意,“最近”的定义取决于本地的**日历时钟**,这很可能是不正确的。即使用严格同步的NTP时钟,一个数据包也可能在时间戳100毫秒(根据发送者的时钟)时发送,并在时间戳99毫秒(根据接收者的时钟)处到达——看起来好像数据包在发送之前已经到达,这是不可能的。
|
||||
|
||||
@ -470,7 +470,7 @@ while (true) {
|
||||
|
||||
通常情况下,一些东西在一个系统中只能有一个。例如:
|
||||
|
||||
* 数据库分区的领导者只能有一个节点,以避免**脑裂(split brain)**(请参阅“[处理节点宕机](ch5.md#处理节点宕机)”)。
|
||||
* 数据库分区的领导者只能有一个节点,以避免**脑裂**(即split brain,请参阅“[处理节点宕机](ch5.md#处理节点宕机)”)。
|
||||
* 特定资源的锁或对象只允许一个事务/客户端持有,以防同时写入和损坏。
|
||||
* 一个特定的用户名只能被一个用户所注册,因为用户名必须唯一标识一个用户。
|
||||
|
||||
|
@ -57,7 +57,7 @@
|
||||
|
||||
* 分区 (Partitioning)
|
||||
|
||||
将一个大型数据库拆分成较小的子集(称为**分区**,即partitions),从而不同的分区可以指派给不同的**节点(node)**(nodes,亦称**分片**,即sharding)。 [第六章](ch6.md)将讨论分区。
|
||||
将一个大型数据库拆分成较小的子集(称为**分区**,即partitions),从而不同的分区可以指派给不同的**节点**(nodes,亦称**分片**,即sharding)。 [第六章](ch6.md)将讨论分区。
|
||||
|
||||
复制和分区是不同的机制,但它们经常同时使用。如[图II-1](img/figii-1.png)所示。
|
||||
|
||||
|
@ -55,7 +55,7 @@
|
||||
|
||||
* 可靠性(Reliability)
|
||||
|
||||
系統在**困境(adversity)**(硬體故障、軟體故障、人為錯誤)中仍可正常工作(正確完成功能,並能達到期望的效能水準)。請參閱“[可靠性](#可靠性)”。
|
||||
系統在**困境**(adversity,比如硬體故障、軟體故障、人為錯誤)中仍可正常工作(正確完成功能,並能達到期望的效能水準)。請參閱“[可靠性](#可靠性)”。
|
||||
|
||||
* 可伸縮性(Scalability)
|
||||
|
||||
@ -254,7 +254,7 @@
|
||||
|
||||
適應某個級別負載的架構不太可能應付10倍於此的負載。如果你正在開發一個快速增長的服務,那麼每次負載發生數量級的增長時,你可能都需要重新考慮架構——或者更頻繁。
|
||||
|
||||
人們經常討論**縱向伸縮(scaling up)**(**垂直伸縮(vertical scaling)**,轉向更強大的機器)和**橫向伸縮(scaling out)** (**水平伸縮(horizontal scaling)**,將負載分佈到多臺小機器上)之間的對立。跨多臺機器分配負載也稱為“**無共享(shared-nothing)**”架構。可以在單臺機器上執行的系統通常更簡單,但高階機器可能非常貴,所以非常密集的負載通常無法避免地需要橫向伸縮。現實世界中的優秀架構需要將這兩種方法務實地結合,因為使用幾臺足夠強大的機器可能比使用大量的小型虛擬機器更簡單也更便宜。
|
||||
人們經常討論**縱向伸縮**(scaling up,也稱為垂直伸縮,即vertical scaling,轉向更強大的機器)和**橫向伸縮**(scaling out,也稱為水平伸縮,即horizontal scaling,將負載分佈到多臺小機器上)之間的對立。跨多臺機器分配負載也稱為“**無共享(shared-nothing)**”架構。可以在單臺機器上執行的系統通常更簡單,但高階機器可能非常貴,所以非常密集的負載通常無法避免地需要橫向伸縮。現實世界中的優秀架構需要將這兩種方法務實地結合,因為使用幾臺足夠強大的機器可能比使用大量的小型虛擬機器更簡單也更便宜。
|
||||
|
||||
有些系統是 **彈性(elastic)** 的,這意味著可以在檢測到負載增加時自動增加計算資源,而其他系統則是手動伸縮(人工分析容量並決定向系統新增更多的機器)。如果負載**極難預測(highly unpredictable)**,則彈性系統可能很有用,但手動伸縮系統更簡單,並且意外操作可能會更少(請參閱“[分割槽再平衡](ch6.md#分割槽再平衡)”)。
|
||||
|
||||
@ -354,7 +354,7 @@
|
||||
|
||||
本章探討了一些關於資料密集型應用的基本思考方式。這些原則將指導我們閱讀本書的其餘部分,那裡將會深入技術細節。
|
||||
|
||||
一個應用必須滿足各種需求才稱得上有用。有一些**功能需求(functional requirements)**(它應該做什麼,比如允許以各種方式儲存,檢索,搜尋和處理資料)以及一些**非功能性需求(nonfunctional )**(通用屬性,例如安全性,可靠性,合規性,可伸縮性,相容性和可維護性)。在本章詳細討論了可靠性,可伸縮性和可維護性。
|
||||
一個應用必須滿足各種需求才稱得上有用。有一些**功能需求**(functional requirements,即它應該做什麼,比如允許以各種方式儲存,檢索,搜尋和處理資料)以及一些**非功能性需求*(nonfunctional,即通用屬性,例如安全性、可靠性、合規性、可伸縮性、相容性和可維護性)。在本章詳細討論了可靠性,可伸縮性和可維護性。
|
||||
|
||||
|
||||
**可靠性(Reliability)** 意味著即使發生故障,系統也能正常工作。故障可能發生在硬體(通常是隨機的和不相關的),軟體(通常是系統性的Bug,很難處理),和人類(不可避免地時不時出錯)。 **容錯技術** 可以對終端使用者隱藏某些型別的故障。
|
||||
|
@ -120,7 +120,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
|
||||
|
||||
Ruby指令碼在記憶體中儲存了一個URL的雜湊表,將每個URL對映到它出現的次數。 Unix管道沒有這樣的雜湊表,而是依賴於對URL列表的排序,在這個URL列表中,同一個URL的只是簡單地重複出現。
|
||||
|
||||
哪種方法更好?這取決於你有多少個不同的URL。對於大多數中小型網站,你可能可以為所有不同網址提供一個計數器(假設我們使用1GB記憶體)。在此例中,作業的**工作集(working set)**(作業需要隨機訪問的記憶體大小)僅取決於不同URL的數量:如果日誌中只有單個URL,重複出現一百萬次,則散列表所需的空間表就只有一個URL加上一個計數器的大小。當工作集足夠小時,記憶體散列表表現良好,甚至在效能較差的膝上型電腦上也可以正常工作。
|
||||
哪種方法更好?這取決於你有多少個不同的URL。對於大多數中小型網站,你可能可以為所有不同網址提供一個計數器(假設我們使用1GB記憶體)。在此例中,作業的**工作集**(working set,即作業需要隨機訪問的記憶體大小)僅取決於不同URL的數量:如果日誌中只有單個URL,重複出現一百萬次,則散列表所需的空間表就只有一個URL加上一個計數器的大小。當工作集足夠小時,記憶體散列表表現良好,甚至在效能較差的膝上型電腦上也可以正常工作。
|
||||
|
||||
另一方面,如果作業的工作集大於可用記憶體,則排序方法的優點是可以高效地使用磁碟。這與我們在“[SSTables和LSM樹](ch3.md#SSTables和LSM樹)”中討論過的原理是一樣的:資料塊可以在記憶體中排序並作為段檔案寫入磁碟,然後多個排序好的段可以合併為一個更大的排序檔案。 歸併排序具有在磁碟上執行良好的順序訪問模式。 (請記住,針對順序I/O進行最佳化是[第三章](ch3.md)中反覆出現的主題,相同的模式在此重現)
|
||||
|
||||
@ -148,7 +148,7 @@ Unix管道的發明者道格·麥克羅伊(Doug McIlroy)在1964年首先描
|
||||
|
||||
如果你希望一個程式的輸出成為另一個程式的輸入,那意味著這些程式必須使用相同的資料格式 —— 換句話說,一個相容的介面。如果你希望能夠將任何程式的輸出連線到任何程式的輸入,那意味著所有程式必須使用相同的I/O介面。
|
||||
|
||||
在Unix中,這種介面是一個**檔案(file)**(更準確地說,是一個檔案描述符)。一個檔案只是一串有序的位元組序列。因為這是一個非常簡單的介面,所以可以使用相同的介面來表示許多不同的東西:檔案系統上的真實檔案,到另一個程序(Unix套接字,stdin,stdout)的通訊通道,裝置驅動程式(比如`/dev/audio`或`/dev/lp0`),表示TCP連線的套接字等等。很容易將這些設計視為理所當然的,但實際上能讓這些差異巨大的東西共享一個統一的介面是非常厲害的,這使得它們可以很容易地連線在一起[^ii]。
|
||||
在Unix中,這種介面是一個**檔案**(file,更準確地說,是一個檔案描述符)。一個檔案只是一串有序的位元組序列。因為這是一個非常簡單的介面,所以可以使用相同的介面來表示許多不同的東西:檔案系統上的真實檔案,到另一個程序(Unix套接字,stdin,stdout)的通訊通道,裝置驅動程式(比如`/dev/audio`或`/dev/lp0`),表示TCP連線的套接字等等。很容易將這些設計視為理所當然的,但實際上能讓這些差異巨大的東西共享一個統一的介面是非常厲害的,這使得它們可以很容易地連線在一起[^ii]。
|
||||
|
||||
[^ii]: 統一介面的另一個例子是URL和HTTP,這是Web的基石。 一個URL標識一個網站上的一個特定的東西(資源),你可以連結到任何其他網站的任何網址。 具有網路瀏覽器的使用者因此可以透過跟隨連結在網站之間無縫跳轉,即使伺服器可能由完全不相關的組織維護。 這個原則現在似乎非常明顯,但它卻是網路取能取得今天成就的關鍵。 之前的系統並不是那麼統一:例如,在公告板系統(BBS)時代,每個系統都有自己的電話號碼和波特率配置。 從一個BBS到另一個BBS的引用必須以電話號碼和調變解調器設定的形式;使用者將不得不掛斷,撥打其他BBS,然後手動找到他們正在尋找的資訊。 直接連結到另一個BBS內的一些內容當時是不可能的。
|
||||
|
||||
|
@ -50,7 +50,7 @@
|
||||
|
||||
在這個**釋出/訂閱**模式中,不同的系統採取各種各樣的方法,並沒有針對所有目的的通用答案。為了區分這些系統,問一下這兩個問題會特別有幫助:
|
||||
|
||||
1. **如果生產者傳送訊息的速度比消費者能夠處理的速度快會發生什麼?** 一般來說,有三種選擇:系統可以丟掉訊息,將訊息放入緩衝佇列,或使用**背壓(backpressure)**(也稱為**流量控制(flow control)**;即阻塞生產者,以免其傳送更多的訊息)。例如Unix管道和TCP就使用了背壓:它們有一個固定大小的小緩衝區,如果填滿,傳送者會被阻塞,直到接收者從緩衝區中取出資料(請參閱“[網路擁塞和排隊](ch8.md#網路擁塞和排隊)”)。
|
||||
1. **如果生產者傳送訊息的速度比消費者能夠處理的速度快會發生什麼?** 一般來說,有三種選擇:系統可以丟掉訊息,將訊息放入緩衝佇列,或使用**背壓**(backpressure,也稱為**流量控制**,即flow control:阻塞生產者,以免其傳送更多的訊息)。例如Unix管道和TCP就使用了背壓:它們有一個固定大小的小緩衝區,如果填滿,傳送者會被阻塞,直到接收者從緩衝區中取出資料(請參閱“[網路擁塞和排隊](ch8.md#網路擁塞和排隊)”)。
|
||||
|
||||
如果訊息被快取在佇列中,那麼理解佇列增長會發生什麼是很重要的。當佇列裝不進記憶體時系統會崩潰嗎?還是將訊息寫入磁碟?如果是這樣,磁碟訪問又會如何影響訊息傳遞系統的效能【6】?
|
||||
|
||||
@ -144,7 +144,7 @@
|
||||
|
||||
為了伸縮超出單個磁碟所能提供的更高吞吐量,可以對日誌進行**分割槽**(按[第六章](ch6.md)的定義)。不同的分割槽可以託管在不同的機器上,使得每個分割槽都有一份能獨立於其他分割槽進行讀寫的日誌。一個主題可以定義為一組攜帶相同型別訊息的分割槽。這種方法如[圖11-3](../img/fig11-3.png)所示。
|
||||
|
||||
在每個分割槽內,代理為每個訊息分配一個單調遞增的序列號或**偏移量(offset)**(在[圖11-3](../img/fig11-3.png)中,框中的數字是訊息偏移量)。這種序列號是有意義的,因為分割槽是僅追加寫入的,所以分割槽內的訊息是完全有序的。沒有跨不同分割槽的順序保證。
|
||||
在每個分割槽內,代理為每個訊息分配一個單調遞增的序列號或**偏移量**(offset,在[圖11-3](../img/fig11-3.png)中,框中的數字是訊息偏移量)。這種序列號是有意義的,因為分割槽是僅追加寫入的,所以分割槽內的訊息是完全有序的。沒有跨不同分割槽的順序保證。
|
||||
|
||||
![](../img/fig11-3.png)
|
||||
|
||||
@ -435,7 +435,7 @@ $$
|
||||
|
||||
**複合事件處理(complex event processing, CEP)** 是20世紀90年代為分析事件流而開發出的一種方法,尤其適用於需要搜尋某些事件模式的應用【65,66】。與正則表示式允許你在字串中搜索特定字元模式的方式類似,CEP允許你指定規則以在流中搜索某些事件模式。
|
||||
|
||||
CEP系統通常使用高層次的宣告式查詢語言,比如SQL,或者圖形使用者介面,來描述應該檢測到的事件模式。這些查詢被提交給處理引擎,該引擎消費輸入流,並在內部維護一個執行所需匹配的狀態機。當發現匹配時,引擎發出一個**複合事件(complex event)**(CEP因此得名),並附有檢測到的事件模式詳情【67】。
|
||||
CEP系統通常使用高層次的宣告式查詢語言,比如SQL,或者圖形使用者介面,來描述應該檢測到的事件模式。這些查詢被提交給處理引擎,該引擎消費輸入流,並在內部維護一個執行所需匹配的狀態機。當發現匹配時,引擎發出一個**複合事件**(即complex event,CEP因此得名),並附有檢測到的事件模式詳情【67】。
|
||||
|
||||
在這些系統中,查詢和資料之間的關係與普通資料庫相比是顛倒的。通常情況下,資料庫會持久儲存資料,並將查詢視為臨時的:當查詢進入時,資料庫搜尋與查詢匹配的資料,然後在查詢完成時丟掉查詢。 CEP引擎反轉了角色:查詢是長期儲存的,來自輸入流的事件不斷流過它們,搜尋匹配事件模式的查詢【68】。
|
||||
|
||||
@ -663,7 +663,7 @@ Apache Flink則使用不同的方法,它會定期生成狀態的滾動存檔
|
||||
|
||||
Storm的Trident基於類似的想法來處理狀態【78】。依賴冪等性意味著隱含了一些假設:重啟一個失敗的任務必須以相同的順序重播相同的訊息(基於日誌的訊息代理能做這些事),處理必須是確定性的,沒有其他節點能同時更新相同的值【98,99】。
|
||||
|
||||
當從一個處理節點故障切換到另一個節點時,可能需要進行**防護(fencing)**(請參閱“[領導者和鎖](ch8.md#領導者和鎖)”),以防止被假死節點干擾。儘管有這麼多注意事項,冪等操作是一種實現**恰好一次語義**的有效方式,僅需很小的額外開銷。
|
||||
當從一個處理節點故障切換到另一個節點時,可能需要進行**防護**(fencing,請參閱“[領導者和鎖](ch8.md#領導者和鎖)”),以防止被假死節點干擾。儘管有這麼多注意事項,冪等操作是一種實現**恰好一次語義**的有效方式,僅需很小的額外開銷。
|
||||
|
||||
#### 失敗後重建狀態
|
||||
|
||||
|
@ -300,7 +300,7 @@ Unix和關係資料庫以非常不同的哲學來處理資訊管理問題。Unix
|
||||
|
||||
#### 流處理器和服務
|
||||
|
||||
當今流行的應用開發風格涉及將功能分解為一組透過同步網路請求(如REST API)進行通訊的**服務(service)**(請參閱“[服務中的資料流:REST與RPC](ch4.md#服務中的資料流:REST與RPC)”)。這種面向服務的架構優於單一龐大應用的優勢主要在於:通過鬆散耦合來提供組織上的可伸縮性:不同的團隊可以專職於不同的服務上,從而減少團隊之間的協調工作(因為服務可以獨立部署和更新)。
|
||||
當今流行的應用開發風格涉及將功能分解為一組透過同步網路請求(如REST API)進行通訊的**服務**(service,請參閱“[服務中的資料流:REST與RPC](ch4.md#服務中的資料流:REST與RPC)”)。這種面向服務的架構優於單一龐大應用的優勢主要在於:通過鬆散耦合來提供組織上的可伸縮性:不同的團隊可以專職於不同的服務上,從而減少團隊之間的協調工作(因為服務可以獨立部署和更新)。
|
||||
|
||||
在資料流中組裝流運算元與微服務方法有很多相似之處【40】。但底層通訊機制是有很大區別:資料流採用單向非同步訊息流,而不是同步的請求/響應式互動。
|
||||
|
||||
@ -438,7 +438,7 @@ MPP資料庫的內部查詢執行圖有著類似的特徵(請參閱“[Hadoop
|
||||
|
||||
處理兩次是資料損壞的一種形式:為同樣的服務向客戶收費兩次(收費太多)或增長計數器兩次(誇大指標)都不是我們想要的。在這種情況下,恰好一次意味著安排計算,使得最終效果與沒有發生錯誤的情況一樣,即使操作實際上因為某種錯誤而重試。我們先前討論過實現這一目標的幾種方法。
|
||||
|
||||
最有效的方法之一是使操作**冪等(idempotent)**(請參閱“[冪等性](ch11.md#冪等性)”);即確保它無論是執行一次還是執行多次都具有相同的效果。但是,將不是天生冪等的操作變為冪等的操作需要一些額外的努力與關注:你可能需要維護一些額外的元資料(例如更新了值的操作ID集合),並在從一個節點故障切換至另一個節點時做好防護(請參閱“[領導者和鎖](ch8.md#領導者和鎖)”)。
|
||||
最有效的方法之一是使操作**冪等**(idempotent,請參閱“[冪等性](ch11.md#冪等性)”);即確保它無論是執行一次還是執行多次都具有相同的效果。但是,將不是天生冪等的操作變為冪等的操作需要一些額外的努力與關注:你可能需要維護一些額外的元資料(例如更新了值的操作ID集合),並在從一個節點故障切換至另一個節點時做好防護(請參閱“[領導者和鎖](ch8.md#領導者和鎖)”)。
|
||||
|
||||
#### 抑制重複
|
||||
|
||||
@ -639,13 +639,13 @@ ACID事務通常既提供及時性(例如線性一致性)也提供完整性
|
||||
|
||||
例如,這種系統可以使用多領導者配置運維,跨越多個數據中心,在區域間非同步複製。任何一個數據中心都可以持續獨立執行,因為不需要同步的跨區域協調。這樣的系統的及時性保證會很弱 —— 如果不引入協調它是不可能是線性一致的 —— 但它仍然可以提供有力的完整性保證。
|
||||
|
||||
在這種情況下,可序列化事務作為維護衍生狀態的一部分仍然是有用的,但它們只能在小範圍內執行,在那裡它們工作得很好【8】。異構分散式事務(如XA事務)(請參閱“[實踐中的分散式事務](ch9.md#實踐中的分散式事務)”)不是必需的。同步協調仍然可以在需要的地方引入(例如在無法恢復的操作之前強制執行嚴格的約束),但是如果只是應用的一小部分地方需要它,沒必要讓所有操作都付出協調的代價。【43】。
|
||||
在這種情況下,可序列化事務作為維護衍生狀態的一部分仍然是有用的,但它們只能在小範圍內執行,在那裡它們工作得很好【8】。異構分散式事務(如XA事務,請參閱“[實踐中的分散式事務](ch9.md#實踐中的分散式事務)”)不是必需的。同步協調仍然可以在需要的地方引入(例如在無法恢復的操作之前強制執行嚴格的約束),但是如果只是應用的一小部分地方需要它,沒必要讓所有操作都付出協調的代價。【43】。
|
||||
|
||||
另一種審視協調與約束的角度是:它們減少了由於不一致而必須做出的道歉數量,但也可能會降低系統的效能和可用性,從而可能增加由於宕機中斷而需要做出的道歉數量。你不可能將道歉數量減少到零,但可以根據自己的需求尋找最佳平衡點 —— 既不存在太多不一致性,又不存在太多可用性問題。
|
||||
|
||||
### 信任但驗證
|
||||
|
||||
我們所有關於正確性,完整性和容錯的討論都基於一些假設,假設某些事情可能會出錯,但其他事情不會。我們將這些假設稱為我們的**系統模型(system model)**(請參閱“[將系統模型對映到現實世界](ch8.md#將系統模型對映到現實世界)”):例如,我們應該假設程序可能會崩潰,機器可能突然斷電,網路可能會任意延遲或丟棄訊息。但是我們也可能假設寫入磁碟的資料在執行`fsync`後不會丟失,記憶體中的資料沒有損壞,而CPU的乘法指令總是能返回正確的結果。
|
||||
我們所有關於正確性,完整性和容錯的討論都基於一些假設,假設某些事情可能會出錯,但其他事情不會。我們將這些假設稱為我們的**系統模型**(system model,請參閱“[將系統模型對映到現實世界](ch8.md#將系統模型對映到現實世界)”):例如,我們應該假設程序可能會崩潰,機器可能突然斷電,網路可能會任意延遲或丟棄訊息。但是我們也可能假設寫入磁碟的資料在執行`fsync`後不會丟失,記憶體中的資料沒有損壞,而CPU的乘法指令總是能返回正確的結果。
|
||||
|
||||
這些假設是相當合理的,因為大多數時候它們都是成立的,如果我們不得不經常擔心計算機出錯,那麼基本上寸步難行。在傳統上,系統模型採用二元方法處理故障:我們假設有些事情可能會發生,而其他事情**永遠**不會發生。實際上,這更像是一個概率問題:有些事情更有可能,其他事情不太可能。問題在於違反我們假設的情況是否經常發生,以至於我們可能在實踐中遇到它們。
|
||||
|
||||
|
22
zh-tw/ch2.md
22
zh-tw/ch2.md
@ -39,7 +39,7 @@
|
||||
|
||||
當時的其他資料庫迫使應用程式開發人員必須考慮資料庫內部的資料表示形式。關係模型致力於將上述實現細節隱藏在更簡潔的介面之後。
|
||||
|
||||
多年來,在資料儲存和查詢方面存在著許多相互競爭的方法。在20世紀70年代和80年代初,網路模型和分層模型曾是主要的選擇,但關係模型隨後佔據了主導地位。物件資料庫在20世紀80年代末和90年代初來了又去。XML資料庫在二十一世紀初出現,但只有小眾採用過。關係模型的每個競爭者都在其時代產生了大量的炒作,但從來沒有持續【2】。
|
||||
多年來,在資料儲存和查詢方面存在著許多相互競爭的方法。在20世紀70年代和80年代初,網狀模型(network model)和分層模型(hierarchical model)曾是主要的選擇,但關係模型(relational model)隨後佔據了主導地位。物件資料庫在20世紀80年代末和90年代初來了又去。XML資料庫在二十一世紀初出現,但只有小眾採用過。關係模型的每個競爭者都在其時代產生了大量的炒作,但從來沒有持續【2】。
|
||||
|
||||
隨著電腦越來越強大和互聯,它們開始用於日益多樣化的目的。關係資料庫非常成功地被推廣到業務資料處理的原始範圍之外更為廣泛的用例上。你今天在網上看到的大部分內容依舊是由關係資料庫來提供支援,無論是線上釋出,討論,社交網路,電子商務,遊戲,軟體即服務生產力應用程式等等內容。
|
||||
|
||||
@ -182,23 +182,23 @@ IMS的設計中使用了一個相當簡單的資料模型,稱為**層次模型
|
||||
|
||||
同文檔資料庫一樣,IMS能良好處理一對多的關係,但是很難應對多對多的關係,並且不支援連線。開發人員必須決定是否複製(非規範化)資料或手動解決從一個記錄到另一個記錄的引用。這些二十世紀六七十年代的問題與現在開發人員遇到的文件資料庫問題非常相似【15】。
|
||||
|
||||
那時人們提出了各種不同的解決方案來解決層次模型的侷限性。其中最突出的兩個是**關係模型(relational model)**(它變成了SQL,統治了世界)和**網路模型(network model)**(最初很受關注,但最終變得冷門)。這兩個陣營之間的“大辯論”在70年代持續了很久時間【2】。
|
||||
那時人們提出了各種不同的解決方案來解決層次模型的侷限性。其中最突出的兩個是**關係模型**(relational model,它變成了SQL,並統治了世界)和**網狀模型**(network model,最初很受關注,但最終變得冷門)。這兩個陣營之間的“大辯論”在70年代持續了很久時間【2】。
|
||||
|
||||
那兩個模式解決的問題與當前的問題相關,因此值得簡要回顧一下那場辯論。
|
||||
|
||||
#### 網路模型
|
||||
#### 網狀模型
|
||||
|
||||
網路模型由一個稱為資料系統語言會議(CODASYL)的委員會進行了標準化,並被數個不同的資料庫廠商實現;它也被稱為CODASYL模型【16】。
|
||||
網狀模型由一個稱為資料系統語言會議(CODASYL)的委員會進行了標準化,並被數個不同的資料庫廠商實現;它也被稱為CODASYL模型【16】。
|
||||
|
||||
CODASYL模型是層次模型的推廣。在層次模型的樹結構中,每條記錄只有一個父節點;在網路模式中,每條記錄可能有多個父節點。例如,“Greater Seattle Area”地區可能是一條記錄,每個居住在該地區的使用者都可以與之相關聯。這允許對多對一和多對多的關係進行建模。
|
||||
|
||||
網路模型中記錄之間的連結不是外來鍵,而更像程式語言中的指標(同時仍然儲存在磁碟上)。訪問記錄的唯一方法是跟隨從根記錄起沿這些鏈路所形成的路徑。這被稱為**訪問路徑(access path)**。
|
||||
網狀模型中記錄之間的連結不是外來鍵,而更像程式語言中的指標(同時仍然儲存在磁碟上)。訪問記錄的唯一方法是跟隨從根記錄起沿這些鏈路所形成的路徑。這被稱為**訪問路徑(access path)**。
|
||||
|
||||
最簡單的情況下,訪問路徑類似遍歷連結串列:從列表頭開始,每次檢視一條記錄,直到找到所需的記錄。但在多對多關係的情況中,數條不同的路徑可以到達相同的記錄,網路模型的程式設計師必須跟蹤這些不同的訪問路徑。
|
||||
最簡單的情況下,訪問路徑類似遍歷連結串列:從列表頭開始,每次檢視一條記錄,直到找到所需的記錄。但在多對多關係的情況中,數條不同的路徑可以到達相同的記錄,網狀模型的程式設計師必須跟蹤這些不同的訪問路徑。
|
||||
|
||||
CODASYL中的查詢是透過利用遍歷記錄列和跟隨訪問路徑表在資料庫中移動遊標來執行的。如果記錄有多個父結點(即多個來自其他記錄的傳入指標),則應用程式程式碼必須跟蹤所有的各種關係。甚至CODASYL委員會成員也承認,這就像在n維資料空間中進行導航【17】。
|
||||
|
||||
儘管手動選擇訪問路徑夠能最有效地利用20世紀70年代非常有限的硬體功能(如磁帶驅動器,其搜尋速度非常慢),但這使得查詢和更新資料庫的程式碼變得複雜不靈活。無論是分層還是網路模型,如果你沒有所需資料的路徑,就會陷入困境。你可以改變訪問路徑,但是必須瀏覽大量手寫資料庫查詢程式碼,並重寫來處理新的訪問路徑。更改應用程式的資料模型是很難的。
|
||||
儘管手動選擇訪問路徑夠能最有效地利用20世紀70年代非常有限的硬體功能(如磁帶驅動器,其搜尋速度非常慢),但這使得查詢和更新資料庫的程式碼變得複雜不靈活。無論是分層還是網狀模型,如果你沒有所需資料的路徑,就會陷入困境。你可以改變訪問路徑,但是必須瀏覽大量手寫資料庫查詢程式碼,並重寫來處理新的訪問路徑。更改應用程式的資料模型是很難的。
|
||||
|
||||
#### 關係模型
|
||||
|
||||
@ -240,7 +240,7 @@ CODASYL中的查詢是透過利用遍歷記錄列和跟隨訪問路徑表在資
|
||||
|
||||
大多數文件資料庫以及關係資料庫中的JSON支援都不會強制文件中的資料採用何種模式。關係資料庫的XML支援通常帶有可選的模式驗證。沒有模式意味著可以將任意的鍵和值新增到文件中,並且當讀取時,客戶端對無法保證文件可能包含的欄位。
|
||||
|
||||
文件資料庫有時稱為**無模式(schemaless)**,但這具有誤導性,因為讀取資料的程式碼通常假定某種結構——即存在隱式模式,但不由資料庫強制執行【20】。一個更精確的術語是**讀時模式(schema-on-read)**(資料的結構是隱含的,只有在資料被讀取時才被解釋),相應的是**寫時模式(schema-on-write)**(傳統的關係資料庫方法中,模式明確,且資料庫確保所有的資料都符合其模式)【21】。
|
||||
文件資料庫有時稱為**無模式(schemaless)**,但這具有誤導性,因為讀取資料的程式碼通常假定某種結構——即存在隱式模式,但不由資料庫強制執行【20】。一個更精確的術語是**讀時模式**(即schema-on-read,資料的結構是隱含的,只有在資料被讀取時才被解釋),相應的是**寫時模式**(即schema-on-write,傳統的關係資料庫方法中,模式明確,且資料庫確保所有的資料都符合其模式)【21】。
|
||||
|
||||
讀時模式類似於程式語言中的動態(執行時)型別檢查,而寫時模式類似於靜態(編譯時)型別檢查。就像靜態和動態型別檢查的相對優點具有很大的爭議性一樣【22】,資料庫中模式的強制性是一個具有爭議的話題,一般來說沒有正確或錯誤的答案。
|
||||
|
||||
@ -513,7 +513,7 @@ db.observations.aggregate([
|
||||
|
||||
但是,要是多對多關係在你的資料中很常見呢?關係模型可以處理多對多關係的簡單情況,但是隨著資料之間的連線變得更加複雜,將資料建模為圖形顯得更加自然。
|
||||
|
||||
一個圖由兩種物件組成:**頂點(vertices)**(也稱為**節點(nodes)** 或**實體(entities)**),和**邊(edges)**( 也稱為**關係(relationships)** 或**弧 (arcs)** )。多種資料可以被建模為一個圖形。典型的例子包括:
|
||||
一個圖由兩種物件組成:**頂點**(vertices,也稱為**節點**,即nodes,或**實體**,即entities),和**邊**(edges,也稱為**關係**,即relationships,或**弧**,即arcs)。多種資料可以被建模為一個圖形。典型的例子包括:
|
||||
|
||||
* 社交圖譜
|
||||
|
||||
@ -821,9 +821,9 @@ SELECT ?personName WHERE {
|
||||
|
||||
SPARQL是一種很好的查詢語言——哪怕語義網從未實現,它仍然可以成為一種應用程式內部使用的強大工具。
|
||||
|
||||
> #### 圖形資料庫與網路模型相比較
|
||||
> #### 圖形資料庫與網狀模型相比較
|
||||
>
|
||||
> 在“[文件資料庫是否在重蹈覆轍?](#文件資料庫是否在重蹈覆轍?)”中,我們討論了CODASYL和關係模型如何競相解決IMS中的多對多關係問題。乍一看,CODASYL的網路模型看起來與圖模型相似。CODASYL是否是圖形資料庫的第二個變種?
|
||||
> 在“[文件資料庫是否在重蹈覆轍?](#文件資料庫是否在重蹈覆轍?)”中,我們討論了CODASYL和關係模型如何競相解決IMS中的多對多關係問題。乍一看,CODASYL的網狀模型看起來與圖模型相似。CODASYL是否是圖形資料庫的第二個變種?
|
||||
>
|
||||
> 不,他們在幾個重要方面有所不同:
|
||||
>
|
||||
|
@ -248,7 +248,7 @@ B樹的基本底層寫操作是用新資料覆寫硬碟上的頁面,並假定
|
||||
|
||||
為了使資料庫能處理異常崩潰的場景,B樹實現通常會帶有一個額外的硬碟資料結構:**預寫式日誌**(WAL,即write-ahead log,也稱為**重做日誌**,即redo log)。這是一個僅追加的檔案,每個B樹的修改在其能被應用到樹本身的頁面之前都必須先寫入到該檔案。當資料庫在崩潰後恢復時,這個日誌將被用來使B樹恢復到一致的狀態【5,20】。
|
||||
|
||||
另外還有一個更新頁面的複雜情況是,如果多個執行緒要同時訪問B樹,則需要仔細的併發控制 —— 否則執行緒可能會看到樹處於不一致的狀態。這通常是透過使用**鎖存器(latches)**(輕量級鎖)保護樹的資料結構來完成。日誌結構化的方法在這方面更簡單,因為它們在後臺進行所有的合併,而不會干擾新接收到的查詢,並且能夠時不時地將舊的段原子交換為新的段。
|
||||
另外還有一個更新頁面的複雜情況是,如果多個執行緒要同時訪問B樹,則需要仔細的併發控制 —— 否則執行緒可能會看到樹處於不一致的狀態。這通常是透過使用**鎖存器**(latches,輕量級鎖)保護樹的資料結構來完成。日誌結構化的方法在這方面更簡單,因為它們在後臺進行所有的合併,而不會干擾新接收到的查詢,並且能夠時不時地將舊的段原子交換為新的段。
|
||||
|
||||
#### B樹的最佳化
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
在大多數情況下,修改應用程式的功能也意味著需要更改其儲存的資料:可能需要使用新的欄位或記錄型別,或者以新方式展示現有資料。
|
||||
|
||||
我們在[第二章](ch2.md)討論的資料模型有不同的方法來應對這種變化。關係資料庫通常假定資料庫中的所有資料都遵循一個模式:儘管可以更改該模式(透過模式遷移,即`ALTER`語句),但是在任何時間點都有且僅有一個正確的模式。相比之下,**讀時模式(schema-on-read)**(或**無模式**,即schemaless)資料庫不會強制一個模式,因此資料庫可以包含在不同時間寫入的新老資料格式的混合(請參閱 “[文件模型中的模式靈活性](ch2.md#文件模型中的模式靈活性)” )。
|
||||
我們在[第二章](ch2.md)討論的資料模型有不同的方法來應對這種變化。關係資料庫通常假定資料庫中的所有資料都遵循一個模式:儘管可以更改該模式(透過模式遷移,即`ALTER`語句),但是在任何時間點都有且僅有一個正確的模式。相比之下,**讀時模式**(schema-on-read,或**無模式**,即schemaless)資料庫不會強制一個模式,因此資料庫可以包含在不同時間寫入的新老資料格式的混合(請參閱 “[文件模型中的模式靈活性](ch2.md#文件模型中的模式靈活性)” )。
|
||||
|
||||
當資料**格式(format)** 或**模式(schema)** 發生變化時,通常需要對應用程式程式碼進行相應的更改(例如,為記錄新增新欄位,然後修改程式開始讀寫該欄位)。但在大型應用程式中,程式碼變更通常不會立即完成:
|
||||
|
||||
|
@ -68,7 +68,7 @@
|
||||
>
|
||||
> 對於非同步複製系統而言,主庫故障時有可能丟失資料。這可能是一個嚴重的問題,因此研究人員仍在研究不丟資料但仍能提供良好效能和可用性的複製方法。 例如,**鏈式複製**【8,9】]是同步複製的一種變體,已經在一些系統(如Microsoft Azure儲存【10,11】)中成功實現。
|
||||
>
|
||||
> 複製的一致性與**共識(consensus)**(使幾個節點就某個值達成一致)之間有著密切的聯絡,[第九章](ch9.md)將詳細地探討這一領域的理論。本章主要討論實踐中資料庫常用的簡單複製形式。
|
||||
> 複製的一致性與**共識**(consensus,使幾個節點就某個值達成一致)之間有著密切的聯絡,[第九章](ch9.md)將詳細地探討這一領域的理論。本章主要討論實踐中資料庫常用的簡單複製形式。
|
||||
>
|
||||
|
||||
### 設定新從庫
|
||||
@ -414,7 +414,7 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
|
||||
>
|
||||
> 已經有一些有趣的研究來自動解決由於資料修改引起的衝突。有幾行研究值得一提:
|
||||
>
|
||||
> * **無衝突複製資料型別(Conflict-free replicated datatypes)**(CRDT)【32,38】是可以由多個使用者同時編輯的集合,對映,有序列表,計數器等的一系列資料結構,它們以合理的方式自動解決衝突。一些CRDT已經在Riak 2.0中實現【39,40】。
|
||||
> * **無衝突複製資料型別(Conflict-free replicated datatypes,CRDT)**【32,38】是可以由多個使用者同時編輯的集合,對映,有序列表,計數器等的一系列資料結構,它們以合理的方式自動解決衝突。一些CRDT已經在Riak 2.0中實現【39,40】。
|
||||
> * **可合併的持久資料結構(Mergeable persistent data structures)**【41】顯式跟蹤歷史記錄,類似於Git版本控制系統,並使用三向合併功能(而CRDT使用雙向合併)。
|
||||
> * **可執行的轉換(operational transformation)**[42]是Etherpad 【30】和Google Docs 【31】等合作編輯應用背後的衝突解決演算法。它是專為同時編輯專案的有序列表而設計的,例如構成文字文件的字元列表。
|
||||
>
|
||||
|
@ -438,7 +438,7 @@ COMMIT;
|
||||
|
||||
#### 比較並設定(CAS)
|
||||
|
||||
在不提供事務的資料庫中,有時會發現一種原子操作:**比較並設定(CAS, Compare And Set)**(先前在“[單物件寫入](#單物件寫入)”中提到)。此操作的目的是為了避免丟失更新:只有當前值從上次讀取時一直未改變,才允許更新發生。如果當前值與先前讀取的值不匹配,則更新不起作用,且必須重試讀取-修改-寫入序列。
|
||||
在不提供事務的資料庫中,有時會發現一種原子操作:**比較並設定(CAS, 即Compare And Set,先前在“[單物件寫入](#單物件寫入)”中提到)。此操作的目的是為了避免丟失更新:只有當前值從上次讀取時一直未改變,才允許更新發生。如果當前值與先前讀取的值不匹配,則更新不起作用,且必須重試讀取-修改-寫入序列。
|
||||
|
||||
例如,為了防止兩個使用者同時更新同一個wiki頁面,可以嘗試類似這樣的方式,只有當用戶開始編輯頁面內容時,才會發生更新:
|
||||
|
||||
@ -593,7 +593,7 @@ COMMIT;
|
||||
|
||||
- 字面意義上地序列順序執行事務(請參閱“[真的序列執行](#真的序列執行)”)
|
||||
- **兩階段鎖定(2PL, two-phase locking)**,幾十年來唯一可行的選擇(請參閱“[兩階段鎖定](#兩階段鎖定)”)
|
||||
- 樂觀併發控制技術,例如**可序列化快照隔離(serializable snapshot isolation)**(請參閱“[可序列化快照隔離](#可序列化快照隔離)”)
|
||||
- 樂觀併發控制技術,例如**可序列化快照隔離**(serializable snapshot isolation,請參閱“[可序列化快照隔離](#可序列化快照隔離)”)
|
||||
|
||||
現在將主要在單節點資料庫的背景下討論這些技術;在[第九章](ch9.md)中,我們將研究如何將它們推廣到涉及分散式系統中多個節點的事務。
|
||||
|
||||
@ -631,7 +631,7 @@ COMMIT;
|
||||
儲存過程在關係型資料庫中已經存在了一段時間了,自1999年以來它們一直是SQL標準(SQL/PSM)的一部分。出於各種原因,它們的名聲有點不太好:
|
||||
|
||||
- 每個資料庫廠商都有自己的儲存過程語言(Oracle有PL/SQL,SQL Server有T-SQL,PostgreSQL有PL/pgSQL等)。這些語言並沒有跟上通用程式語言的發展,所以從今天的角度來看,它們看起來相當醜陋和陳舊,而且缺乏大多數程式語言中能找到的庫的生態系統。
|
||||
- 與應用伺服器相比,在資料庫中執行的程式碼管理困難,除錯困難,版本控制和部署起來也更為尷尬,更難測試,更難和用於監控的指標收集系統相整合。
|
||||
- 在資料庫中執行的程式碼難以管理:與應用伺服器相比,它更難除錯,更難以保持版本控制和部署,更難測試,並且難以整合到指標收集系統來進行監控。
|
||||
- 資料庫通常比應用伺服器對效能敏感的多,因為單個數據庫例項通常由許多應用伺服器共享。資料庫中一個寫得不好的儲存過程(例如,佔用大量記憶體或CPU時間)會比在應用伺服器中相同的程式碼造成更多的麻煩。
|
||||
|
||||
但是這些問題都是可以克服的。現代的儲存過程實現放棄了PL/SQL,而是使用現有的通用程式語言:VoltDB使用Java或Groovy,Datomic使用Java或Clojure,而Redis使用Lua。
|
||||
@ -763,7 +763,7 @@ WHERE room_id = 123 AND
|
||||
|
||||
相比之下,**序列化快照隔離**是一種**樂觀(optimistic)** 的併發控制技術。在這種情況下,樂觀意味著,如果存在潛在的危險也不阻止事務,而是繼續執行事務,希望一切都會好起來。當一個事務想要提交時,資料庫檢查是否有什麼不好的事情發生(即隔離是否被違反);如果是的話,事務將被中止,並且必須重試。只有可序列化的事務才被允許提交。
|
||||
|
||||
樂觀併發控制是一個古老的想法【52】,其優點和缺點已經爭論了很長時間【53】。如果存在很多**爭用(contention)**(很多事務試圖訪問相同的物件),則表現不佳,因為這會導致很大一部分事務需要中止。如果系統已經接近最大吞吐量,來自重試事務的額外負載可能會使效能變差。
|
||||
樂觀併發控制是一個古老的想法【52】,其優點和缺點已經爭論了很長時間【53】。如果存在很多**爭用**(contention,即很多事務試圖訪問相同的物件),則表現不佳,因為這會導致很大一部分事務需要中止。如果系統已經接近最大吞吐量,來自重試事務的額外負載可能會使效能變差。
|
||||
|
||||
但是,如果有足夠的備用容量,並且事務之間的爭用不是太高,樂觀的併發控制技術往往比悲觀的要好。可交換的原子操作可以減少爭用:例如,如果多個事務同時要增加一個計數器,那麼應用增量的順序(只要計數器不在同一個事務中讀取)就無關緊要了,所以併發增量可以全部應用且無需衝突。
|
||||
|
||||
|
16
zh-tw/ch8.md
16
zh-tw/ch8.md
@ -156,7 +156,7 @@
|
||||
|
||||
過早地宣告一個節點已經死了是有問題的:如果這個節點實際上是活著的,並且正在執行一些動作(例如,傳送一封電子郵件),而另一個節點接管,那麼這個動作可能會最終執行兩次。我們將在“[知識、真相與謊言](#知識、真相與謊言)”以及[第九章](ch9.md)和[第十一章](ch11.md)中更詳細地討論這個問題。
|
||||
|
||||
當一個節點被宣告死亡時,它的職責需要轉移到其他節點,這會給其他節點和網路帶來額外的負擔。如果系統已經處於高負荷狀態,則過早宣告節點死亡會使問題更嚴重。特別是如果節點實際上沒有死亡,只是由於過載導致其響應緩慢;這時將其負載轉移到其他節點可能會導致**級聯失效(cascading failure)**(在極端情況下,所有節點都宣告對方死亡,所有節點都將停止工作)。
|
||||
當一個節點被宣告死亡時,它的職責需要轉移到其他節點,這會給其他節點和網路帶來額外的負擔。如果系統已經處於高負荷狀態,則過早宣告節點死亡會使問題更嚴重。特別是如果節點實際上沒有死亡,只是由於過載導致其響應緩慢;這時將其負載轉移到其他節點可能會導致**級聯失效**(即cascading failure,表示在極端情況下,所有節點都宣告對方死亡,所有節點都將停止工作)。
|
||||
|
||||
設想一個虛構的系統,其網路可以保證資料包的最大延遲——每個資料包要麼在一段時間內傳送,要麼丟失,但是傳遞永遠不會比$d$更長。此外,假設你可以保證一個非故障節點總是在一段時間內處理一個請求$r$。在這種情況下,你可以保證每個成功的請求在$2d + r$時間內都能收到響應,如果你在此時間內沒有收到響應,則知道網路或遠端節點不工作。如果這是成立的,$2d + r$ 會是一個合理的超時設定。
|
||||
|
||||
@ -186,7 +186,7 @@
|
||||
|
||||
所有這些因素都會造成網路延遲的變化。當系統接近其最大容量時,排隊延遲的變化範圍特別大:擁有足夠備用容量的系統可以輕鬆排空佇列,而在高利用率的系統中,很快就能積累很長的佇列。
|
||||
|
||||
在公共雲和多租戶資料中心中,資源被許多客戶共享:網路連結和交換機,甚至每個機器的網絡卡和CPU(在虛擬機器上執行時)。批處理工作負載(如MapReduce)(請參閱[第十章](ch10.md))能夠很容易使網路連結飽和。由於無法控制或瞭解其他客戶對共享資源的使用情況,如果附近的某個人(嘈雜的鄰居)正在使用大量資源,則網路延遲可能會發生劇烈變化【28,29】。
|
||||
在公共雲和多租戶資料中心中,資源被許多客戶共享:網路連結和交換機,甚至每個機器的網絡卡和CPU(在虛擬機器上執行時)。批處理工作負載(如MapReduce,請參閱[第十章](ch10.md))能夠很容易使網路連結飽和。由於無法控制或瞭解其他客戶對共享資源的使用情況,如果附近的某個人(嘈雜的鄰居)正在使用大量資源,則網路延遲可能會發生劇烈變化【28,29】。
|
||||
|
||||
在這種環境下,你只能透過實驗方式選擇超時:在一段較長的時期內、在多臺機器上測量網路往返時間的分佈,以確定延遲的預期變化。然後,考慮到應用程式的特性,可以確定**故障檢測延遲**與**過早超時風險**之間的適當折衷。
|
||||
|
||||
@ -214,7 +214,7 @@
|
||||
|
||||
如果想透過電路傳輸檔案,你得預測一個頻寬分配。如果你猜的太低,傳輸速度會不必要的太慢,導致網路容量閒置。如果你猜的太高,電路就無法建立(因為如果無法保證其頻寬分配,網路不能建立電路)。因此,將電路用於突發資料傳輸會浪費網路容量,並且使傳輸不必要地緩慢。相比之下,TCP動態調整資料傳輸速率以適應可用的網路容量。
|
||||
|
||||
已經有一些嘗試去建立同時支援電路交換和分組交換的混合網路,比如ATM[^iii]。InfiniBand有一些相似之處【35】:它在鏈路層實現了端到端的流量控制,從而減少了在網路中排隊的需要,儘管它仍然可能因鏈路擁塞而受到延遲【36】。透過仔細使用**服務質量(quality of service,)**(QoS,資料包的優先順序和排程)和**准入控制(admission control)**(限速傳送器),可以在分組網路上類比電路交換,或提供統計上的**有限延遲**【25,32】。
|
||||
已經有一些嘗試去建立同時支援電路交換和分組交換的混合網路,比如ATM[^iii]。InfiniBand有一些相似之處【35】:它在鏈路層實現了端到端的流量控制,從而減少了在網路中排隊的需要,儘管它仍然可能因鏈路擁塞而受到延遲【36】。透過仔細使用**服務質量**(quality of service,即QoS,資料包的優先順序和排程)和**准入控制**(admission control,限速傳送器),可以在分組網路上類比電路交換,或提供統計上的**有限延遲**【25,32】。
|
||||
|
||||
[^iii]: **非同步傳輸模式(Asynchronous Transfer Mode, ATM)** 在20世紀80年代是乙太網的競爭對手【32】,但在電話網核心交換機之外並沒有得到太多的採用。它與自動櫃員機(也稱為自動取款機)無關,儘管共用一個縮寫詞。或許,在一些平行的世界裡,網際網路是基於像ATM這樣的東西,因此它們的網際網路影片通話可能比我們的更可靠,因為它們不會遭受包的丟失和延遲。
|
||||
|
||||
@ -250,7 +250,7 @@
|
||||
7. 這個快取條目何時到期?
|
||||
8. 日誌檔案中此錯誤訊息的時間戳是什麼?
|
||||
|
||||
[例1-4](ch1.md)測量了**持續時間(durations)**(例如,請求傳送與響應接收之間的時間間隔),而[例5-8](ch5.md)描述了**時間點(point in time)**(在特定日期,特定時間發生的事件)。
|
||||
[例1-4](ch1.md)測量了**持續時間**(durations,例如,請求傳送與響應接收之間的時間間隔),而[例5-8](ch5.md)描述了**時間點**(point in time,在特定日期和和特定時間發生的事件)。
|
||||
|
||||
在分散式系統中,時間是一件棘手的事情,因為通訊不是即時的:訊息透過網路從一臺機器傳送到另一臺機器需要時間。收到訊息的時間總是晚於傳送的時間,但是由於網路中的可變延遲,我們不知道晚了多少時間。這個事實導致有時很難確定在涉及多臺機器時發生事情的順序。
|
||||
|
||||
@ -280,13 +280,13 @@
|
||||
|
||||
如果NTP協議檢測到計算機的本地石英鐘比NTP伺服器要更快或更慢,則可以調整單調鍾向前走的頻率(這稱為**偏移(skewing)** 時鐘)。預設情況下,NTP允許時鐘速率增加或減慢最高至0.05%,但NTP不能使單調時鐘向前或向後跳轉。單調時鐘的解析度通常相當好:在大多數系統中,它們能在幾微秒或更短的時間內測量時間間隔。
|
||||
|
||||
在分散式系統中,使用單調鍾測量**經過時間(elapsed time)**(比如超時)通常很好,因為它不假定不同節點的時鐘之間存在任何同步,並且對測量的輕微不準確性不敏感。
|
||||
在分散式系統中,使用單調鍾測量**經過時間**(elapsed time,比如超時)通常很好,因為它不假定不同節點的時鐘之間存在任何同步,並且對測量的輕微不準確性不敏感。
|
||||
|
||||
### 時鐘同步與準確性
|
||||
|
||||
單調鐘不需要同步,但是日曆時鐘需要根據NTP伺服器或其他外部時間源來設定才能有用。不幸的是,我們獲取時鐘的方法並不像你所希望的那樣可靠或準確——硬體時鐘和NTP可能會變幻莫測。舉幾個例子:
|
||||
|
||||
* 計算機中的石英鐘不夠精確:它會**漂移(drifts)**(執行速度快於或慢於預期)。時鐘漂移取決於機器的溫度。 Google假設其伺服器時鐘漂移為200 ppm(百萬分之一)【41】,相當於每30秒與伺服器重新同步一次的時鐘漂移為6毫秒,或者每天重新同步的時鐘漂移為17秒。即使一切工作正常,此漂移也會限制可以達到的最佳準確度。
|
||||
* 計算機中的石英鐘不夠精確:它會**漂移**(drifts,即執行速度快於或慢於預期)。時鐘漂移取決於機器的溫度。 Google假設其伺服器時鐘漂移為200 ppm(百萬分之一)【41】,相當於每30秒與伺服器重新同步一次的時鐘漂移為6毫秒,或者每天重新同步的時鐘漂移為17秒。即使一切工作正常,此漂移也會限制可以達到的最佳準確度。
|
||||
* 如果計算機的時鐘與NTP伺服器的時鐘差別太大,可能會拒絕同步,或者本地時鐘將被強制重置【37】。任何觀察重置前後時間的應用程式都可能會看到時間倒退或突然跳躍。
|
||||
* 如果某個節點被NTP伺服器的防火牆意外阻塞,有可能會持續一段時間都沒有人會注意到。有證據表明,這在實踐中確實發生過。
|
||||
* NTP同步只能和網路延遲一樣好,所以當你在擁有可變資料包延遲的擁塞網路上時,NTP同步的準確性會受到限制。一個實驗表明,當透過網際網路同步時,35毫秒的最小誤差是可以實現的,儘管偶爾的網路延遲峰值會導致大約一秒的誤差。根據配置,較大的網路延遲會導致NTP客戶端完全放棄。
|
||||
@ -327,7 +327,7 @@
|
||||
|
||||
* 資料庫寫入可能會神祕地消失:具有滯後時鐘的節點無法覆蓋之前具有快速時鐘的節點寫入的值,直到節點之間的時鐘偏差消逝【54,55】。此方案可能導致一定數量的資料被悄悄丟棄,而未嚮應用報告任何錯誤。
|
||||
* LWW無法區分**高頻順序寫入**(在[圖8-3](../img/fig8-3.png)中,客戶端B的增量操作**一定**發生在客戶端A的寫入之後)和**真正併發寫入**(寫入者意識不到其他寫入者)。需要額外的因果關係跟蹤機制(例如版本向量),以防止違背因果關係(請參閱“[檢測併發寫入](ch5.md#檢測併發寫入)”)。
|
||||
* 兩個節點很可能獨立地生成具有相同時間戳的寫入,特別是在時鐘僅具有毫秒解析度的情況下。為了解決這樣的衝突,還需要一個額外的**決勝值(tiebreaker)**(可以簡單地是一個大隨機數),但這種方法也可能會導致違背因果關係【53】。
|
||||
* 兩個節點很可能獨立地生成具有相同時間戳的寫入,特別是在時鐘僅具有毫秒解析度的情況下。為了解決這樣的衝突,還需要一個額外的**決勝值**(tiebreaker,可以簡單地是一個大隨機數),但這種方法也可能會導致違背因果關係【53】。
|
||||
|
||||
因此,儘管透過保留最“最近”的值並放棄其他值來解決衝突是很誘惑人的,但是要注意,“最近”的定義取決於本地的**日曆時鐘**,這很可能是不正確的。即使用嚴格同步的NTP時鐘,一個數據包也可能在時間戳100毫秒(根據傳送者的時鐘)時傳送,並在時間戳99毫秒(根據接收者的時鐘)處到達——看起來好像資料包在傳送之前已經到達,這是不可能的。
|
||||
|
||||
@ -470,7 +470,7 @@ while (true) {
|
||||
|
||||
通常情況下,一些東西在一個系統中只能有一個。例如:
|
||||
|
||||
* 資料庫分割槽的領導者只能有一個節點,以避免**腦裂(split brain)**(請參閱“[處理節點宕機](ch5.md#處理節點宕機)”)。
|
||||
* 資料庫分割槽的領導者只能有一個節點,以避免**腦裂**(即split brain,請參閱“[處理節點宕機](ch5.md#處理節點宕機)”)。
|
||||
* 特定資源的鎖或物件只允許一個事務/客戶端持有,以防同時寫入和損壞。
|
||||
* 一個特定的使用者名稱只能被一個使用者所註冊,因為使用者名稱必須唯一標識一個使用者。
|
||||
|
||||
|
@ -57,7 +57,7 @@
|
||||
|
||||
* 分割槽 (Partitioning)
|
||||
|
||||
將一個大型資料庫拆分成較小的子集(稱為**分割槽**,即partitions),從而不同的分割槽可以指派給不同的**節點(node)**(nodes,亦稱**分片**,即sharding)。 [第六章](ch6.md)將討論分割槽。
|
||||
將一個大型資料庫拆分成較小的子集(稱為**分割槽**,即partitions),從而不同的分割槽可以指派給不同的**節點**(nodes,亦稱**分片**,即sharding)。 [第六章](ch6.md)將討論分割槽。
|
||||
|
||||
複製和分割槽是不同的機制,但它們經常同時使用。如[圖II-1](../img/figii-1.png)所示。
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user