Py学习  »  NoSql

【架构师必知必会】常见的NoSQL数据库种类以及使用场景

禅与计算机程序设计艺术 • 3 年前 • 441 次点击  
前言
NoSQL数据库是构建高并发大数据互联网应用的必备组件,熟悉理解各种类型nosql数据的特点和应用场景,对提高架构能力有巨大帮助,是高级后端架构师必须理解的知识点。
NoSQL 不是一个工具,而是由多个互补和竞争的工具组成的生态系统。标有NoSQL绰号的工具,提供了一种替代“基于SQL的关系数据库系统”来存储数据的方法。
NoSQL is not a tool, but an ecosystem composed of several complimentary and competing tools.The tools branded with the NoSQL monicker provide an alternative to SQL-based relational database systems for storing data.
NoSQL 前传:SQL 和关系模型

SQL (结构化查询语言)数据库在二十世纪 70 年代初开始流行。

当时,存储非常昂贵,因此软件工程师对其数据库进行规范化以减少数据重复。

此外,二十世纪70年代的软件工程师,通常也遵循瀑布式软件开发模型。在开始开发之前详细规划项目。软件工程师精心创建复杂的实体关系 (E-R) 图确保已仔细考虑需要存储的所有数据。

由于采用了这种预先计划模型,因此如果开发周期中的需求发生变化,软件工程师就难以调整适应。结果,项目经常超出预算,超过截止日期,无法满足用户需求。

SQL 是一种用于查询数据的声明性语言。声明式语言是一种程序员指定他们希望系统做什么的语言,而不是程序性地定义系统应该如何做的语言。一些示例包括:找到员工 39 的记录,从他们的整个记录中仅投影出员工姓名和电话号码,将员工记录过滤到从事会计工作的员工记录,计算每个部门的员工人数,或加入员工的数据表与经理表。
SQL is a declarative language for querying data. A declarative language is one in which a programmer specifies what they want the system to do, rather than procedurally defining how the system should do it. A few examples include: find the record for employee 39, project out only the employee name and phone number from their entire record, filter employee records to those that work in accounting, count the employees in each department, or join the data from the employees table with the managers table.
大致上,SQL 允许您提出这些问题,而无需考虑数据在磁盘上的布局方式、使用哪些索引来访问数据或使用哪些算法来处理数据。大多数关系数据库的一个重要架构组件是 查询优化器,它决定执行许多逻辑上等效的查询计划中的哪一个以最快地回答查询。这些优化器通常比普通数据库用户更好,但有时他们没有足够的信息或系统模型过于简单,无法生成最有效的执行。
To a first approximation, SQL allows you to ask these questions without thinking about how the data is laid out on disk, which indices to use to access the data, or what algorithms to use to process the data. A significant architectural component of most relational databases is a query optimizer, which decides which of the many logically equivalent query plans to execute to most quickly answer a query. These optimizers are often better than the average database user, but sometimes they do not have enough information or have too simple a model of the system in order to generate the most efficient execution.
关系数据库是实践中最常用的数据库,遵循关系数据模型。在这个模型中,不同的现实世界实体存储在不同的中。例如,所有员工都可能存储在 Employees 表中,所有部门都可能存储在 Departments 表中。表的每一行都有存储在列中的各种属性。例如,员工可能有员工 ID、薪水、出生日期和名字/姓氏。这些属性中的每一个都将存储在 Employees 表的一列中。
Relational databases, which are the most common databases used in practice, follow the relational data model. In this model, different real-world entities are stored in different tables. For example, all employees might be stored in an Employees table, and all departments might be stored in a Departments table. Each row of a table has various properties stored in columns. For example, employees might have an employee id, salary, birth date, and first/last names. Each of these properties will be stored in a column of the Employees table.
关系模型与 SQL 密切相关。简单的 SQL 查询,例如过滤器,检索其字段匹配某些测试的所有记录(例如,employeeid = 3,或薪水 > $20000)。更复杂的构造会导致数据库做一些额外的工作,例如连接来自多个表的数据(例如,员工 3 工作的部门名称是什么?)。其他复杂的结构,如聚合(例如,我员工的平均工资是多少?)可以导致全表扫描。
The relational model goes hand-in-hand with SQL. Simple SQL queries, such as filters, retrieve all records whose field matches some test (e.g., employeeid = 3, or salary > $20000). More complex constructs cause the database to do some extra work, such as joining data from multiple tables (e.g., what is the name of the department in which employee 3 works?). Other complex constructs such as aggregates (e.g., what is the average salary of my employees?) can lead to full-table scans.
关系数据模型定义了高度结构化的实体,它们之间具有严格的关系。使用 SQL 查询此模型允许复杂的数据遍历,而无需过多的自定义开发。但是,这种建模和查询的复杂性有其局限性:
  • 复杂性导致不可预测性。SQL 的表现力使得推断每个查询的成本以及工作负载的成本具有挑战性。虽然更简单的查询语言可能会使应用程序逻辑复杂化,但它们可以更轻松地配置仅响应简单请求的数据存储系统。
  • 有很多方法可以对问题进行建模。关系数据模型是严格的:分配给每个表的模式指定每一行中的数据。如果我们存储结构化程度较低的数据,或者存储的列中变化较大的行,则关系模型可能会受到不必要的限制。同样,应用程序开发人员可能不会发现关系模型非常适合对每种数据进行建模。例如,很多应用程序逻辑是用面向对象的语言编写的,包括列表、队列和集合等高级概念,一些程序员希望他们的持久层对此进行建模。
  • 如果数据增长超过了一台服务器的容量,那么数据库中的表将不得不跨计算机进行分区。为避免 JOIN 必须跨网络获取不同表中的数据,我们必须对其进行非规范化。非规范化将人们可能想要一次查找的不同表中的所有数据存储在一个地方。这使我们的数据库看起来像一个键查找存储系统,让我们想知道其他数据模型可能更适合这些数据。
任意丢弃多年的设计考虑通常是不明智的。当您考虑将数据存储在数据库中时,请考虑 SQL 和关系模型,它们以数十年的研究和开发为后盾,提供丰富的建模功能,并为复杂的操作提供易于理解的保证。当您遇到特定问题时,NoSQL 是一个不错的选择,例如大量数据、大量工作负载或 SQL 和关系数据库可能未针对其进行优化的困难数据建模决策。
The relational data model defines highly structured entities with strict relationships between them. Querying this model with SQL allows complex data traversals without too much custom development. The complexity of such modeling and querying has its limits, though:
  • Complexity leads to unpredictability. SQL's expressiveness makes it challenging to reason about the cost of each query, and thus the cost of a workload. While simpler query languages might complicate application logic, they make it easier to provision data storage systems, which only respond to simple requests.
  • There are many ways to model a problem. The relational data model is strict: the schema assigned to each table specifies the data in each row. If we are storing less structured data, or rows with more variance in the columns they store, the relational model may be needlessly restrictive. Similarly, application developers might not find the relational model perfect for modeling every kind of data. For example, a lot of application logic is written in object-oriented languages and includes high-level concepts such as lists, queues, and sets, and some programmers would like their persistence layer to model this.
  • If the data grows past the capacity of one server, then the tables in the database will have to be partitioned across computers. To avoid JOINs having to cross the network in order to get data in different tables, we will have to denormalize it. Denormalization stores all of the data from different tables that one might want to look up at once in a single place. This makes our database look like a key-lookup storage system, leaving us wondering what other data models might better suit the data.

It's generally not wise to discard many years of design considerations arbitrarily. When you consider storing your data in a database, consider SQL and the relational model, which are backed by decades of research and development, offer rich modeling capabilities, and provide easy-to-understand guarantees about complex operations. NoSQL is a good option when you have a specific problem, such as large amounts of data, a massive workload, or a difficult data modeling decision for which SQL and relational databases might not have been optimized.

SQL vs NoSQL - Difference B/W SQL & NoSQL Databases


什么是NoSQL?

NoSQL 数据库有哪些类型?

随着时间的推移,出现了四种主要的 NoSQL 数据库类型:[文档数据库] (https://www.mongodb.com/document-databases),[键值数据库] (https://www.mongodb.com/key-value-database),宽列存储数据库和图形数据库让我们一起来看看每种类型。

NoSQL 灵感(NoSQL Inspirations)
NoSQL 运动的大部分灵感来自研究社区的论文。虽然许多论文都是 NoSQL 系统设计决策的核心,但有两篇论文尤为突出。
The NoSQL movement finds much of its inspiration in papers from the research community. While many papers are at the core of design decisions in NoSQL systems, two stand out in particular.
Google 的 BigTable [ CDG+06 ] 提出了一个有趣的数据模型,它有助于对多列历史数据进行排序存储。使用基于范围的分层分区方案将数据分发到多个服务器,并以严格的一致性更新数据(我们最终将在 第 13.5 节中定义的概念)。
Google's BigTable [CDG+06] presents an interesting data model, which facilitates sorted storage of multi-column historical data. Data is distributed to multiple servers using a hierarchical range-based partitioning scheme, and data is updated with strict consistency (a concept that we will eventually define in Section 13.5).
Amazon 的 Dynamo [ DHJ+07 ] 使用不同的面向密钥的分布式数据存储。Dynamo 的数据模型更简单,将键映射到特定于应用程序的数据块。分区模型对故障更具弹性,但通过称为最终一致性的更宽松的数据一致性方法来实现该目标。
Amazon's Dynamo [DHJ+07] uses a different key-oriented distributed datastore. Dynamo's data model is simpler, mapping keys to application-specific blobs of data. The partitioning model is more resilient to failure, but accomplishes that goal through a looser data consistency approach called eventual consistency.
我们将更详细地研究这些概念中的每一个,但重要的是要了解其中的许多概念可以混合和匹配。一些 NoSQL 系统(例如 HBase 1)与 BigTable 设计密切相关。另一个名为 Voldemort 2的 NoSQL 系统复制了 Dynamo 的许多功能。还有其他 NoSQL 项目,如 Cassandra 3,从 BigTable(其数据模型)和 Dynamo(其分区和一致性方案)中获取了一些功能。
We will dig into each of these concepts in more detail, but it is important to understand that many of them can be mixed and matched. Some NoSQL systems such as HBase sticks closely to the BigTable design. Another NoSQL system named Voldemort replicates many of Dynamo's features. Still other NoSQL projects such as Cassandra have taken some features from BigTable (its data model) and others from Dynamo (its partitioning and consistency schemes).
特点和注意事项 Characteristics and Considerations
NoSQL 系统与庞大的 SQL 标准分道扬镳,并为构建存储解决方案提供更简单但零碎的解决方案。构建这些系统的信念是,通过简化数据库对数据的操作方式,架构师可以更好地预测查询的性能。在许多 NoSQL 系统中,复杂的查询逻辑留给了应用程序,由于查询中缺乏可变性,导致数据存储具有更可预测的查询性能。
NoSQL systems part ways with the hefty SQL standard and offer simpler but piecemeal solutions for architecting storage solutions. These systems were built with the belief that in simplifying how a database operates over data, an architect can better predict the performance of a query. In many NoSQL systems, complex query logic is left to the application, resulting in a data store with more predictable query performance because of the lack of variability in queries.

NoSQL 系统不仅仅是对关系数据的声明性查询。事务语义、一致性和持久性是银行等组织对数据库需求的保证。 当将几个潜在的复杂操作合并为一个时,事务(Transaction)提供了一种全有或全无的保证,例如从一个账户中扣除资金并将资金添加到另一个账户中。 一致性 确保当值更新时,后续查询将看到更新后的值。 持久性保证一旦一个值被更新,它将被写入稳定的存储(例如硬盘)并且在数据库崩溃时可以恢复。

NoSQL systems part with more than just declarative queries over the relational data. Transactional semantics, consistency, and durability are guarantees that organizations such as banks demand of databases. Transactions provide an all-or-nothing guarantee when combining several potentially complex operations into one, such as deducting money from one account and adding the money to another. Consistency ensures that when a value is updated, subsequent queries will see the updated value. Durability guarantees that once a value is updated, it will be written to stable storage (such as a hard drive) and recoverable if the database crashes.

NoSQL 系统放宽了其中一些保证,对于许多非银行应用程序来说,这一决定可以提供可接受和可预测的行为以换取改进的性能。这些放松与数据模型和查询语言的变化相结合,通常可以在数据增长超出单台机器的能力时,更轻松地跨多台机器安全地分区数据库。

NoSQL systems relax some of these guarantees, a decision which, for many non-banking applications, can provide acceptable and predictable behavior in exchange for improved performance. These relaxations, combined with data model and query language changes, often make it easier to safely partition a database across multiple machines when the data grows beyond a single machine's capability.

NoSQL 系统仍处于起步阶段。本章描述的系统的架构决策证明了不同用户的需求。总结几个开源项目的架构特点最大的挑战是每一个都是一个移动的目标。请记住,各个系统的细节会发生变化。当您在 NoSQL 系统之间进行选择时,您可以使用本章来指导您的思考过程,而不是您的逐个功能产品选择。

NoSQL systems are still very much in their infancy. The architectural decisions that go into the systems described in this chapter are a testament to the requirements of various users. The biggest challenge in summarizing the architectural features of several open source projects is that each one is a moving target. Keep in mind that the details of individual systems will change. When you pick between NoSQL systems, you can use this chapter to guide your thought process, but not your feature-by-feature product selection.
当您考虑 NoSQL 系统时,这里有一个注意事项路线图:
  • 数据和查询模型:您的数据是表示为行、对象、数据结构还是文档?你能要求数据库计算多条记录的聚合吗?
  • 持久性当你改变一个值时,它会立即进入稳定存储吗?它是否存储在多台机器上以防其中一台机器崩溃?
  • 可扩展性您的数据是否适合单个服务器?读写量是否需要多个磁盘来处理工作负载?
  • 分区出于可扩展性、可用性或持久性的原因,数据是否需要存在于多个服务器上?您如何知道哪个记录在哪个服务器上?
  • 一致性如果您在多个服务器上对记录进行分区和复制,当记录更改时服务器如何协调?
  • 事务语义当你运行一系列操作时,一些数据库允许你将它们包装在一个事务中,这为事务和当前运行的所有其他事务提供了 ACID(原子性、一致性、隔离性和持久性)保证的一些子集。您的业务逻辑是否需要这些通常伴随性能权衡的保证?
  • 单服务器性能如果您想将数据安全地存储在磁盘上,哪些磁盘数据结构最适合读取密集型或写入密集型工作负载?写入磁盘是您的瓶颈吗?
  • 分析工作负载我们将非常关注运行响应式的以用户为中心的 Web 应用程序所需的那种查找繁重的工作负载。在许多情况下,您将希望构建数据集大小的报告,例如聚合多个用户的统计数据。您的用例和工具链是否需要此类功能?

As you think about NoSQL systems, here is a roadmap of considerations:
  • Data and query model: Is your data represented as rows, objects, data structures, or documents? Can you ask the database to calculate aggregates over multiple records?
  • Durability: When you change a value, does it immediately go to stable storage? Does it get stored on multiple machines in case one crashes?
  • Scalability: Does your data fit on a single server? Do the amount of reads and writes require multiple disks to handle the workload?
  • Partitioning: For scalability, availability, or durability reasons, does the data need to live on multiple servers? How do you know which record is on which server?
  • Consistency: If you've partitioned and replicated your records across multiple servers, how do the servers coordinate when a record changes?
  • Transactional semantics: When you run a series of operations, some databases allow you to wrap them in a transaction, which provides some subset of ACID (Atomicity, Consistency, Isolation, and Durability) guarantees on the transaction and all others currently running. Does your business logic require these guarantees, which often come with performance tradeoffs?
  • Single-server performance: If you want to safely store data on disk, what on-disk data structures are best-geared toward read-heavy or write-heavy workloads? Is writing to disk your bottleneck?
  • Analytical workloads: We're going to pay a lot of attention to lookup-heavy workloads of the kind you need to run a responsive user-focused web application. In many cases, you will want to build dataset-sized reports, aggregating statistics across multiple users for example. Does your use-case and toolchain require such functionality?



There are various ways to classify NoSQL databases, with different categories and subcategories, some of which overlap. What follows is a non-exhaustive classification by data model, with examples:

TypeNotable examples of this type
Key–value cacheApache Ignite, Couchbase, Coherence, eXtreme Scale, Hazelcast, Infinispan, Memcached, Redis, Velocity
Key–value storeAzure Cosmos DB, ArangoDB, Amazon DynamoDB, Aerospike, Couchbase, ScyllaDB
Key–value store (eventually consistent)Azure Cosmos DB, Oracle NoSQL Database, Riak, Voldemort
Key–value store (ordered)FoundationDB, InfinityDB, LMDB, MemcacheDB
Tuple storeApache River, GigaSpaces, Tarantool, TIBCO ActiveSpaces, OpenLink Virtuoso
TriplestoreAllegroGraph, MarkLogic, Ontotext-OWLIM, Oracle NoSQL database, Profium Sense, Virtuoso Universal Server
Object databaseObjectivity/DB, Perst, ZopeDB, db4o, GemStone/S, InterSystems Caché, JADE, ObjectDatabase++, ObjectDB, ObjectStore, ODABA, Realm, OpenLink Virtuoso, Versant Object Database, ZODB
Document storeAzure Cosmos DB, ArangoDB, BaseX, Clusterpoint, Couchbase, CouchDB, DocumentDB, eXist-db, IBM Domino, MarkLogic, MongoDB, RavenDB, Qizx, RethinkDB, Elasticsearch, OrientDB
Wide Column StoreAzure Cosmos DB, Amazon DynamoDB, Bigtable, Cassandra, Google Cloud Datastore, HBase, Hypertable, ScyllaDB
Native multi-model databaseArangoDB, Azure Cosmos DB, OrientDB, MarkLogic, Apache Ignite,[22][23] Couchbase, FoundationDB, Oracle Database
Graph databaseAzure Cosmos DB, AllegroGraph, ArangoDB, InfiniteGraph, Apache Giraph, MarkLogic, Neo4J, OrientDB, Virtuoso
Multivalue databaseD3 Pick database, Extensible Storage Engine (ESE/NT), InfinityDB, InterSystems Caché, jBASE Pick database, mvBase Rocket Software, mvEnterprise Rocket Software, Northgate Information Solutions Reality (the original Pick/MV Database), OpenQM, Revelation Software's OpenInsight (Windows) and Advanced Revelation (DOS), UniData Rocket U2, UniVerse Rocket U2

NoSQL 数据和查询模型

NoSQL Data and Query Models
数据库的数据模型指定数据的逻辑组织方式。它的查询模型规定了如何检索和更新数据。常见的数据模型有关系模型、面向键的存储模型或各种图形模型。您可能听说过的查询语言包括 SQL、键查找和 MapReduce。NoSQL 系统结合了不同的数据和查询模型,导致了不同的架构考虑。
The data model of a database specifies how data is logically organized. Its query model dictates how the data can be retrieved and updated. Common data models are the relational model, key-oriented storage model, or various graph models.Query languages you might have heard of include SQL, key lookups, and MapReduce. NoSQL systems combine different data and query models, resulting in different architectural considerations.

基于键的 NoSQL 数据模型——一切皆是映射

Key-based NoSQL Data Models——All is mapping.
NoSQL 系统通常通过将对数据集的查找限制在单个字段中来脱离关系模型和 SQL 的完整表达能力。例如,即使员工有很多属性,您也可能只能通过她的 ID 检索员工。因此,NoSQL 系统中的大多数查询都是基于键查找的。程序员选择一个键来标识每个数据项,并且在大多数情况下,只能通过在数据库中查找键来检索项目。
NoSQL systems often part with the relational model and the full expressivity of SQL by restricting lookups on a dataset to a single field. For example, even if an employee has many properties, you might only be able to retrieve an employee by her ID. As a result, most queries in NoSQL systems are key lookup-based. The programmer selects a key to identify each data item, and can, for the most part, only retrieve items by performing a lookup for their key in the database.

在基于键查找的系统中,复杂的连接操作或相同数据的多键检索可能需要创造性地使用键名。希望通过员工 ID 查找员工和查找部门中所有员工的程序员可能会创建两个键类型。例如,键employee:30将指向员工 ID 为 30 的员工记录,并且employee_departments:20可能包含部门 20 中所有员工的列表。连接操作被推送到应用程序逻辑中:要检索部门 20 中的员工,应用程序首先检索列表来自 key 的员工 ID employee_departments:20,然后循环查找employee:ID员工列表中的每个员工 ID。

In key lookup-based systems, complex join operations or multiple-key retrieval of the same data might require creative uses of key names. A programmer wishing to look up an employee by his employee ID and to look up all employees in a department might create two key types. For example, the key employee:30 would point to an employee record for employee ID 30, and employee_departments:20 might contain a list of all employees in department 20. A join operation gets pushed into application logic: to retrieve employees in department 20, an application first retrieves a list of employee IDs from key employee_departments:20, and then loops over key lookups for each  employee:ID in the employee list.


键查找模型是有益的,因为它意味着数据库具有一致的查询模式——整个工作负载由性能相对统一且可预测的键查找组成。分析以查找应用程序的缓慢部分更简单,因为所有复杂的操作都驻留在应用程序代码中。另一方面,数据模型逻辑和业务逻辑现在更加紧密地交织在一起,这混淆了抽象。
The key lookup model is beneficial because it means that the database has a consistent query patternthe entire workload consists of key lookups whose performance is relatively uniform and predictable. Profiling to find the slow parts of an application is simpler, since all complex operations reside in the application code. On the flip side, the data model logic and business logic are now more closely intertwined, which muddles abstraction.

让我们快速了解与每个键关联的数据。各种 NoSQL 系统在此领域提供不同的解决方案。
Let's quickly touch on the data associated with each key. Various NoSQL systems offer different solutions in this space.

键值存储


NoSQL 存储的最简单形式是键值存储。每个键都映射到一个包含任意数据的值。NoSQL 存储不知道其有效负载的内容,只是将数据传递给应用程序。在我们的员工数据库示例中,可以将密钥映射employee:30到包含 JSON 或二进制格式(例如 Protocol Buffers 4、Thrift 5或 Avro 6 )的 blob ,以便封装有关员工 30 的信息。
如果开发人员使用结构化格式为键存储复杂数据,她必须对应用程序空间中的数据进行操作:键值数据存储通常不提供基于键值的某些属性查询键的机制。键值存储以其查询模型的简单性而著称,通常由 setgetdelete原语组成,但由于其值的不透明性而放弃了添加简单的数据库内过滤功能的能力。Voldemort 基于亚马逊的 Dynamo,提供分布式键值存储。BDB 7提供了一个具有键值接口的持久性库。



Key-Value Stores

The simplest form of NoSQL store is a key-value store. Each key is mapped to a value containing arbitrary data. The NoSQL store has no knowledge of the contents of its payload, and simply delivers the data to the application. In our Employee database example, one might map the key employee:30 to a blob containing JSON or a binary format such as Protocol Buffers4, Thrift5, or Avro6 in order to encapsulate the information about employee 30.

If a developer uses structured formats to store complex data for a key, she must operate against the data in application space: a key-value data store generally offers no mechanisms for querying for keys based on some property of their values. Key-value stores shine in the simplicity of their query model, usually consisting of setget, and delete primitives, but discard the ability to add simple in-database filtering capabilities due to the opacity of their values. Voldemort, which is based on Amazon's Dynamo, provides a distributed key-value store. BDB7 offers a persistence library that has a key-value interface.


关键数据结构存储

键数据结构存储,由 Redis 8流行起来,为每个值分配一个类型。在 Redis 中,值可以采用的可用类型有整数、字符串、列表、集合和有序集合。除了 setget/之外delete,特定于类型的命令(例如整数的递增/递减或列表的推送/弹出)向查询模型添加功能,而不会显着影响请求的性能特征。通过提供简单的特定于类型的功能,同时避免聚合或连接等多键操作,Redis 平衡了功能和性能。


Key-Data Structure Stores

Key-data structure stores, made popular by Redis8, assign each value a type. In Redis, the available types a value can take on are integer, string, list, set, and sorted set. In addition to set/get/delete, type-specific commands, such as increment/decrement for integers, or push/pop for lists, add functionality to the query model without drastically affecting performance characteristics of requests. By providing simple type-specific functionality while avoiding multi-key operations such as aggregation or joins, Redis balances functionality and performance.

关键文件存储

密钥文档存储,例如 CouchDB 9、MongoDB 10和 Riak 11,将密钥映射到一些包含结构化信息的文档。这些系统以 JSON 或类似 JSON 的格式存储文档。它们存储列表和字典,它们可以递归地嵌入到另一个中。
MongoDB 将键空间分成多个集合,这样 Employees 和 Department 的键就不会发生冲突。CouchDB 和 Riak 将类型跟踪留给了开发人员。文档存储的自由性和复杂性是一把双刃剑:应用程序开发人员在建模文档方面有很大的自由度,但基于应用程序的查询逻辑可能会变得极其复杂。


Key-Document Stores

Key-document stores, such as CouchDB9, MongoDB10, and Riak11, map a key to some document that contains structured information. These systems store documents in a JSON or JSON-like format. They store lists and dictionaries, which can be embedded recursively inside one-another.

MongoDB separates the keyspace into collections, so that keys for Employees and Department, for example, do not collide. CouchDB and Riak leave type-tracking to the developer. The freedom and complexity of document stores is a double-edged sword: application developers have a lot of freedom in modeling their documents, but application-based query logic can become exceedingly complex.

BigTable 列族存储

HBase 和 Cassandra 的数据模型基于 Google 的 BigTable 使用的模型。在此模型中,一个键标识一行,其中包含存储在一个或多个列族 (CF) 中的数据。在 CF 中,每一行可以包含多列。每列中的值都带有时间戳,因此行-列映射的多个版本可以存在于一个 CF 中。
从概念上讲,可以将列族视为存储形式复杂的键(行 ID、CF、列、时间戳),映射到按键排序的值。这种设计导致将大量功能推入键空间的数据建模决策。它特别擅长对带有时间戳的历史数据进行建模。该模型自然支持稀疏列放置,因为没有某些列的行 ID 不需要这些列的显式 NULL 值。另一方面,具有很少或没有 NULL 值的列仍必须在每一行中存储列标识符,这会导致更大的空间消耗。
每个项目数据模型都与原始 BigTable 模型有很多不同,但 Cassandra 的变化最为显着。Cassandra 在每个 CF 中引入了超级列的概念,以允许另一个级别的映射、建模和索引。它还取消了局部性组的概念,出于性能原因,局部性组可以物理地将多个列族存储在一起。

BigTable Column Family Stores

HBase and Cassandra base their data model on the one used by Google's BigTable. In this model, a key identifies a row, which contains data stored in one or more Column Families (CFs). Within a CF, each row can contain multiple columns. The values within each column are timestamped, so that several versions of a row-column mapping can live within a CF.

Conceptually, one can think of Column Families as storing complex keys of the form (row ID, CF, column, timestamp), mapping to values which are sorted by their keys. This design results in data modeling decisions which push a lot of functionality into the keyspace. It is particularly good at modeling historical data with timestamps. The model naturally supports sparse column placement since row IDs that do not have certain columns do not need an explicit NULL value for those columns. On the flip side, columns which have few or no NULL values must still store the column identifier with each row, which leads to greater space consumption.

Each project data model differs from the original BigTable model in various ways, but Cassandra's changes are most notable. Cassandra introduces the notion of a supercolumn within each CF to allow for another level of mapping, modeling, and indexing. It also does away with a notion of locality groups, which can physically store multiple column families together for performance reasons.


图存储

一类 NoSQL 存储是图形存储。并非所有数据都是生来平等的,存储和查询数据的关系和面向键的数据模型并非对所有数据都是最佳的。图是计算机科学中的基本数据结构,HyperGraphDB 12 和 Neo4J 13等系统是两种流行的用于存储图结构数据的 NoSQL 存储系统。图存储在几乎所有方面都与我们迄今为止讨论的其他存储不同:数据模型、数据遍历和查询模式、磁盘上数据的物理布局、多台机器的分布以及查询的事务语义。鉴于篇幅所限,我们无法公正地看待这些明显的差异,但您应该知道,某些类别的数据可能更适合作为图形进行存储和查询。

Graph Storage

One class of NoSQL stores are graph stores. Not all data is created equal, and the relational and key-oriented data models of storing and querying data are not the best for all data. Graphs are a fundamental data structure in computer science, and systems such as HyperGraphDB12 and Neo4J13 are two popular NoSQL storage systems for storing graph-structured data. Graph stores differ from the other stores we have discussed thus far in almost every way: data models, data traversal and querying patterns, physical layout of data on disk, distribution to multiple machines, and the transactional semantics of queries. We can not do these stark differences justice given space limitations, but you should be aware that certain classes of data may be better stored and queried as a graph.


复杂查询

NoSQL 系统中的仅键查找有明显的例外。MongoDB 允许您根据任意数量的属性为您的数据编制索引,并具有一种相对高级的语言来指定您要检索的数据。基于 BigTable 的系统支持扫描器迭代列族并通过列上的过滤器选择特定项目。CouchDB 允许您创建不同的数据视图,并在您的表中运行 MapReduce 任务以促进更复杂的查找和更新。大多数系统都绑定到 Hadoop 或其他 MapReduce 框架以执行数据集规模的分析查询。

Complex Queries

There are notable exceptions to key-only lookups in NoSQL systems. MongoDB allows you to index your data based on any number of properties and has a relatively high-level language for specifying which data you want to retrieve. BigTable-based systems support scanners to iterate over a column family and select particular items by a filter on a column. CouchDB allows you to create different views of the data, and to run MapReduce tasks across your table to facilitate more complex lookups and updates. Most of the systems have bindings to Hadoop or another MapReduce framework to perform dataset-scale analytical queries.


事务

NoSQL 系统通常将性能优先于 事务语义。其他基于 SQL 的系统允许将任何语句集(从简单的主键行检索到多个表之间的复杂连接,然后对多个字段进行平均)放置在一个事务中。
这些 SQL 数据库将在事务之间提供 ACID 保证。在事务中运行多个操作是原子的(ACID 中的 A),这意味着所有操作都发生或都不发生。一致性(C)确保事务使数据库处于一致、未损坏的状态。隔离(I)确保如果两个事务触及相同的记录,它们将不会踩到对方的脚。持久性(D,在下一节中广泛介绍)确保事务一旦提交,就会存储在安全的地方。
符合 ACID 的事务使开发人员可以轻松地推断出他们的数据状态,从而使开发人员保持理智。想象多笔交易,每笔交易都有多个步骤(例如,首先检查银行账户的价值,然后减去 60 美元,然后更新价值)。符合 ACID 的数据库通常在交错这些步骤的同时仍能跨所有事务提供正确结果方面受到限制。这种对正确性的推动导致了经常出乎意料的性能特征,在这种情况下,缓慢的交易可能会导致原本快速的交易排队等候。
大多数 NoSQL 系统选择性能而不是完整的 ACID 保证,但确实在键级别提供保证:对同一键的两个操作将被序列化,避免键值对的严重损坏。对于许多应用程序,此决定不会造成明显的正确性问题,并且可以更规律地执行快速操作。但是,它确实将应用程序设计和正确性的更多考虑事项留给了开发人员。
Redis 是无事务趋势的明显例外。在单个服务器上,它提供了一个MULTI以原子方式一致地组合多个操作的WATCH命令,以及一个允许隔离的命令。其他系统提供较低级别的 测试和设置功能,提供一些隔离保证。

Transactions

NoSQL systems generally prioritize performance over transactional semantics. Other SQL-based systems allow any set of statements—from a simple primary key row retrieval, to a complicated join between several tables which is then subsequently averaged across several fields—to be placed in a transaction.

These SQL databases will offer ACID guarantees between transactions. Running multiple operations in a transaction is Atomic (the A in ACID), meaning all or none of the operations happen. Consistency (the C) ensures that the transaction leaves the database in a consistent, uncorrupted state. Isolation (the I) makes sure that if two transactions touch the same record, they will do without stepping on each other's feet. Durability (the D, covered extensively in the next section), ensures that once a transaction is committed, it's stored in a safe place.

ACID-compliant transactions keep developers sane by making it easy to reason about the state of their data. Imagine multiple transactions, each of which has multiple steps (e.g., first check the value of a bank account, then subtract $60, then update the value). ACID-compliant databases often are limited in how they can interleave these steps while still providing a correct result across all transactions. This push for correctness results in often-unexpected performance characteristics, where a slow transaction might cause an otherwise quick one to wait in line.

Most NoSQL systems pick performance over full ACID guarantees, but do provide guarantees at the key level: two operations on the same key will be serialized, avoiding serious corruption to key-value pairs. For many applications, this decision will not pose noticeable correctness issues, and will allow quick operations to execute with more regularity. It does, however, leave more considerations for application design and correctness in the hands of the developer.

Redis is the notable exception to the no-transaction trend. On a single server, it provides a MULTI command to combine multiple operations atomically and consistently, and a WATCH command to allow isolation. Other systems provide lower-level test-and-set functionality which provides some isolation guarantees.


无模式存储

许多 NoSQL 系统的一个交叉属性是数据库中缺乏模式实施。即使在文档存储和面向列族的存储中,相似实体的属性也不需要相同。这样做的好处是支持较少的结构化数据要求,并且在动态修改模式时需要较少的性能开销。该决定将更多责任留给了应用程序开发人员,他们现在必须更加谨慎地进行编程。例如,lastname 员工记录中缺少某个属性是需要纠正的错误,还是当前正在通过系统传播的架构更新?在依赖 sloppy-schema NoSQL系统的项目进行几次迭代后,数据和模式版本控制在应用程序级代码中很常见。


Schema-free Storage

A cross-cutting property of many NoSQL systems is the lack of schema enforcement in the database. Even in document stores and column family-oriented stores, properties across similar entities are not required to be the same. This has the benefit of supporting less structured data requirements and requiring less performance expense when modifying schemas on-the-fly. The decision leaves more responsibility to the application developer, who now has to program more defensively. For example, is the lack of a lastname property on an employee record an error to be rectified, or a schema update which is currently propagating through the system? Data and schema versioning is common in application-level code after a few iterations of a project which relies on sloppy-schema NoSQL systems.


数据持久性 Data Durability

理想情况下,存储系统上的所有数据修改都将立即安全地持久化并复制到多个位置以避免数据丢失。但是,保证数据安全与性能存在一定的张力,不同的NoSQL系统 为了提升性能做出不同的数据持久化保证。失败场景多种多样,并非所有 NoSQL 系统都能保护您免受这些问题的影响。
一个简单而常见的故障场景是服务器重启或断电。在这种情况下,数据持久性涉及将数据从内存移动到硬盘,硬盘不需要电源来存储数据。硬盘故障是通过将数据复制到辅助设备来处理的,无论是同一台机器中的其他硬盘驱动器(RAID 镜像)还是网络上的其他机器。然而,数据中心可能无法在导致相关故障的事件(例如龙卷风)中幸存下来,并且一些组织甚至将数据复制到相距几个飓风宽度的数据中心的备份。写入硬盘驱动器并将数据复制到多个服务器或数据中心的成本很高,因此不同的 NoSQL 系统会牺牲持久性保证来换取性能。

Ideally, all data modifications on a storage system would immediately be safely persisted and replicated to multiple locations to avoid data loss. However, ensuring data safety is in tension with performance, and different NoSQL systems make different data durability guarantees in order to improve performance. Failure scenarios are varied and numerous, and not all NoSQL systems protect you against these issues.


A simple and common failure scenario is a server restart or power loss. Data durability in this case involves having moved the data from memory to a hard disk, which does not require power to store data. Hard disk failure is handled by copying the data to secondary devices, be they other hard drives in the same machine (RAID mirroring) or other machines on the network. However, a data center might not survive an event which causes correlated failure (a tornado, for example), and some organizations go so far as to copy data to backups in data centers several hurricane widths apart. Writing to hard drives and copying data to multiple servers or data centers is expensive, so different NoSQL systems trade off durability guarantees for performance.


单机持久性 Single-server Durability

最简单的持久性形式是单服务器持久性,它确保任何数据修改都能在服务器重启或断电后继续存在。这通常意味着将更改的数据写入磁盘,这通常会成为您的工作负载的瓶颈。即使您命令操作系统将数据写入磁盘文件,操作系统也可能会缓冲写入,避免立即修改磁盘,以便它可以将多个写入组合成一个操作。只有fsync发出系统调用时,操作系统才会尽最大努力确保缓冲更新持久保存到磁盘。
典型的硬盘驱动器每秒可以执行 100-200 次随机访问(寻道),并且限制为 30-100 MB/秒的顺序写入。在这两种情况下,内存都可以快几个数量级。确保高效的单服务器持久性意味着限制系统产生的随机写入次数,并增加每个硬盘驱动器的顺序写入次数。理想情况下,您希望系统最小化fsync调用之间的写入次数,最大化连续写入的次数,同时永远不要告诉用户他们的数据已成功写入磁盘,直到写入完成fsync。让我们介绍一些提高单服务器持久性保证性能的技术。

The simplest form of durability is a single-server durability, which ensures that any data modification will survive a server restart or power loss. This usually means writing the changed data to disk, which often bottlenecks your workload. Even if you order your operating system to write data to an on-disk file, the operating system may buffer the write, avoiding an immediate modification on disk so that it can group several writes together into a single operation. Only when the fsync system call is issued does the operating system make a best-effort attempt to ensure that buffered updates are persisted to disk.

Typical hard drives can perform 100-200 random accesses (seeks) per second, and are limited to 30-100 MB/sec of sequential writes. Memory can be orders of magnitudes faster in both scenarios. Ensuring efficient single-server durability means limiting the number of random writes your system incurs, and increasing the number of sequential writes per hard drive. Ideally, you want a system to minimize the number of writes between fsync calls, maximizing the number of those writes that are sequential, all the while never telling the user their data has been successfully written to disk until that write has been  fsynced. Let's cover a few techniques for improving performance of single-server durability guarantees.


控制fsync频率

Memcached 14是一个系统示例,它不提供磁盘持久性以换取极快的内存操作。当服务器重新启动时,该服务器上的数据就消失了:这导致了良好的缓存和较差的持久数据存储。
Redis 为开发人员提供了何时调用 fsync. 开发人员可以在每次更新后强制fsync调用,这是一种缓慢而安全的选择。为了获得更好的性能,Redis 可以fsync每 N 秒写入一次。在最坏的情况下,您将丢失最后 N 秒的操作,这对于某些用途来说可能是可以接受的。最后,对于持久性不重要的用例(维护粗粒度统计信息,或使用 Redis 作为缓存),开发人员可以fsync完全关闭调用:操作系统最终会将数据刷新到磁盘,但无法保证何时刷新会发生。

Control fsync Frequency

Memcached14 is an example of a system which offers no on-disk durability in exchange for extremely fast in-memory operations. When a server restarts, the data on that server is gone: this makes for a good cache and a poor durable data store.

Redis offers developers several options for when to call fsync. Developers can force an fsync call after every update, which is the slow and safe choice. For better performance, Redis can fsync its writes every N seconds. In a worst-case scenario, the you will lose last N seconds worth of operations, which may be acceptable for certain uses. Finally, for use cases where durability is not important (maintaining coarse-grained statistics, or using Redis as a cache), the developer can turn off fsync calls entirely: the operating system will eventually flush the data to disk, but without guarantees of when this will happen.


通过日志记录增加顺序写入

B+树等多种数据结构可帮助 NoSQL 系统快速从磁盘检索数据。对这些结构的更新会导致数据结构文件中随机位置的更新,如果您fsync在每次更新后进行更新,则会导致每次更新多次随机写入。为了减少随机写入,Cassandra、HBase、Redis 和 Riak 等系统将更新操作附加到称为日志的顺序写入文件中。系统使用的其他数据结构只是定期fsync更新,而日志是频繁 fsync更新的。通过将日志视为崩溃后数据库的真实状态,这些存储引擎能够将随机更新转变为顺序更新。
虽然 MongoDB 等 NoSQL 系统在其数据结构中执行就地写入,但其他系统甚至更进一步进行日志记录。Cassandra 和 HBase 使用了一种从 BigTable 借来的技术,将它们的日志和查找数据结构组合成一个日志结构的合并树Riak 通过日志结构的哈希表提供了类似的功能。CouchDB 对传统的 B+Tree 进行了修改,使得对数据结构的所有更改都附加到物理存储上的结构中。这些技术提高了写入吞吐量,但需要定期进行日志压缩以防止日志无限增长。

Increase Sequential Writes by Logging

Several data structures, such as B+Trees, help NoSQL systems quickly retrieve data from disk. Updates to those structures result in updates in random locations in the data structures' files, resulting in several random writes per update if you fsync after each update. To reduce random writes, systems such as Cassandra, HBase, Redis, and Riak append update operations to a sequentially-written file called a log. While other data structures used by the system are only periodically fsynced, the log is frequently fsynced. By treating the log as the ground-truth state of the database after a crash, these storage engines are able to turn random updates into sequential ones.

While NoSQL systems such as MongoDB perform writes in-place in their data structures, others take logging even further. Cassandra and HBase use a technique borrowed from BigTable of combining their logs and lookup data structures into one log-structured merge tree. Riak provides similar functionality with a log-structured hash table. CouchDB has modified the traditional B+Tree so that all changes to the data structure are appended to the structure on physical storage. These techniques result in improved write throughput, but require a periodic log compaction to keep the log from growing unbounded.


通过分组写入提高吞吐量

Cassandra 将短窗口内的多个并发更新分组为单个fsync调用。这种称为group commit的设计导致每次更新的延迟更高,因为用户必须等待多个并发更新才能确认他们自己的更新。延迟增加伴随着吞吐量的增加,因为单个fsync在撰写本文时,每个 HBase 更新都持久保存到 Hadoop 分布式文件系统 (HDFS) 15提供的底层存储中,该系统最近出现了补丁以允许支持尊重 fsync和组提交的附加。

Increase Throughput by Grouping Writes

Cassandra groups multiple concurrent updates within a short window into a single fsync  call. This design, called group commit, results in higher latency per update, as users have to wait on several concurrent updates to have their own update be acknowledged. The latency bump comes at an increase in throughput, as multiple log appends can happen with a single fsync. As of this writing, every HBase update is persisted to the underlying storage provided by the Hadoop Distributed File System (HDFS)15, which has recently seen patches to allow support of appends that respect fsync and group commit.


多服务器持久性

由于硬盘驱动器和机器经常发生无法修复的故障,因此有必要跨机器复制重要数据。许多 NoSQL 系统为数据提供多服务器持久性。
Redis 采用传统的主从方式来复制数据。对主机执行的所有操作都以类似日志的方式传达给从机,从机在自己的硬件上复制操作。如果主服务器发生故障,从服务器可以介入并提供从主服务器接收到的操作日志状态中的数据。此配置可能会导致一些数据丢失,因为主服务器在向用户确认操作之前不会确认从服务器已在其日志中持久化操作。
CouchDB 促进了类似形式的定向复制,其中服务器可以配置为将更改复制到其他存储上的文档。
MongoDB 提供了副本集的概念,其中一些服务器负责存储每个文档。MongoDB 为开发人员提供了确保所有副本都已收到更新或在不确保副本具有最新数据的情况下继续操作的选项。许多其他分布式 NoSQL 存储系统支持数据的多服务器复制。
HBase 建立在 HDFS 之上,通过 HDFS 获得多服务器持久性。在将控制权返回给用户之前,所有写入都被复制到两个或多个 HDFS 节点,确保多服务器持久性。
Riak、Cassandra 和 Voldemort 支持更多可配置的复制形式。有细微差别,这三个系统都允许用户指定N,最终应该拥有数据副本的机器数量,以及WN,在将控制权返回给用户之前应该确认数据已经写入的机器数量。
为了处理整个数据中心停止服务的情况,需要跨数据中心的多服务器复制。Cassandra、HBase 和 Voldemort 具有机架感知配置,这些配置指定各种机器所在的机架或数据中心。通常,在远程服务器确认更新之前阻止用户的请求会导致过多的延迟。当跨广域网执行更新到备份数据中心时,更新会在没有确认的情况下流式传输。

Multi-server Durability

Because hard drives and machines often irreparably fail, copying important data across machines is necessary. Many NoSQL systems offer multi-server durability for data.

Redis takes a traditional master-slave approach to replicating data. All operations executed against a master are communicated in a log-like fashion to slave machines, which replicate the operations on their own hardware. If a master fails, a slave can step in and serve the data from the state of the operation log that it received from the master. This configuration might result in some data loss, as the master does not confirm that the slave has persisted an operation in its log before acknowledging the operation to the user. 

CouchDB facilitates a similar form of directional replication, where servers can be configured to replicate changes to documents on other stores.

MongoDB provides the notion of replica sets, where some number of servers are responsible for storing each document. MongoDB gives developers the option of ensuring that all replicas have received updates, or to proceed without ensuring that replicas have the most recent data. Many of the other distributed NoSQL storage systems support multi-server replication of data. 

HBase, which is built on top of HDFS, receives multi-server durability through HDFS. All writes are replicated to two or more HDFS nodes before returning control to the user, ensuring multi-server durability.

Riak, Cassandra, and Voldemort support more configurable forms of replication. With subtle differences, all three systems allow the user to specify N, the number of machines which should ultimately have a copy of the data, and W<N, the number of machines that should confirm the data has been written before returning control to the user.

To handle cases where an entire data center goes out of service, multi-server replication across data centers is required. Cassandra, HBase, and Voldemort have rack-aware configurations, which specify the rack or data center in which various machines are located. In general, blocking the user's request until a remote server has acknowledged an update incurs too much latency. Updates are streamed without confirmation when performed across wide area networks to backup data centers.

扩展性能

刚刚谈到处理失败,让我们想象一个更美好的情况:成功!如果您构建的系统取得成功,您的数据存储将成为承受负载压力的组件之一。解决此类问题的一种廉价而肮脏的解决方案是扩大现有机器的规模:投资更多的 RAM 和磁盘来处理一台机器上的工作负载。随着更多的成功,将资金投入更昂贵的硬件将变得不可行。此时,您将不得不复制数据并将请求分散到多台机器上以分配负载。这种方法称为横向扩展,通过系统的水平可扩展性来衡量。
理想的水平可扩展性目标是线性可扩展性,其中将存储系统中的机器数量加倍会使系统的查询容量加倍。这种可扩展性的关键在于数据如何跨机器传播。分片是将您的读写工作负载拆分到多台机器上以扩展您的存储系统的行为。分片是许多系统设计的基础,即 Cassandra、HBase、Voldemort 和 Riak,以及最近的 MongoDB 和 Redis。CouchDB 等一些项目专注于单服务器性能,不提供分片的系统内解决方案,但辅助项目提供协调器,以在多台机器上的独立安装之间划分工作负载。
让我们介绍一些您可能会遇到的可互换术语。我们将交替使用术语分片分区。术语machineservernode指的是一些存储部分分区数据的物理计算机。最后,集群是指参与存储系统的一组机器。
分片意味着没有一台机器必须处理整个数据集的写入工作量,但没有一台机器可以回答有关整个数据集的查询。大多数 NoSQL 系统在其数据和查询模型中都是面向键的,很少有查询涉及整个数据集。由于这些系统中数据的主要访问方法是基于键的,因此分片通常也是基于键的:键的某些功能决定了存储键值对的机器。
我们将介绍两种定义密钥-机器映射的方法:散列分区和范围分区。

Scaling for Performance

Having just spoken about handling failure, let's imagine a rosier situation: success! If the system you build reaches success, your data store will be one of the components to feel stress under load. A cheap and dirty solution to such problems is to scale up your existing machinery: invest in more RAM and disks to handle the workload on one machine. With more success, pouring money into more expensive hardware will become infeasible. At this point, you will have to replicate data and spread requests across multiple machines to distribute load. This approach is called scale out, and is measured by the horizontal scalability of your system.

The ideal horizontal scalability goal is linear scalability, in which doubling the number of machines in your storage system doubles the query capacity of the system. The key to such scalability is in how the data is spread across machines. Sharding is the act of splitting your read and write workload across multiple machines to scale out your storage system. Sharding is fundamental to the design of many systems, namely Cassandra, HBase, Voldemort, and Riak, and more recently MongoDB and Redis. Some projects such as CouchDB focus on single-server performance and do not provide an in-system solution to sharding, but secondary projects provide coordinators to partition the workload across independent installations on multiple machines.

Let's cover a few interchangeable terms you might encounter. We will use the terms sharding and partitioning interchangeably. The terms machineserver, or node refer to some physical computer which stores part of the partitioned data. Finally, a cluster or ring refers to the set of machines which participate in your storage system.

Sharding means that no one machine has to handle the write workload on the entire dataset, but no one machine can answer queries about the entire dataset. Most NoSQL systems are key-oriented in both their data and query models, and few queries touch the entire dataset anyway. Because the primary access method for data in these systems is key-based, sharding is typically key-based as well: some function of the key determines the machine on which a key-value pair is stored. 

We'll cover two methods of defining the key-machine mapping: hash partitioning and range partitioning.


除非必须,否则不要分片

分片增加了系统的复杂性,您应该尽可能避免使用分片。让我们介绍两种无需分片即可扩展的方法:只读副本和缓存。

读取副本

许多存储系统看到的读取请求多于写入请求。在这些情况下,一个简单的解决方案是在多台机器上制作数据副本。所有写请求仍会转到主节点。读取请求发送到复制数据的机器,并且相对于写入主机上的数据通常略微陈旧。
如果您已经在主从配置中复制数据以实现多服务器持久性,这在 Redis、CouchDB 或 MongoDB 中很常见,那么读从服务器可以减轻写主服务器的一些负载。一些查询,例如数据集的聚合摘要,可能很昂贵并且通常不需要最新的新鲜度,可以针对从属副本执行。一般来说,你对内容新鲜度的要求越低,你就越能依靠只读从机来提高只读查询性能。

缓存

在您的系统中缓存最流行的内容通常效果出奇地好。Memcached 将多个服务器上的内存块专用于缓存数据存储中的数据。Memcached 客户端利用多个水平可扩展性技巧在不同服务器上的 Memcached 安装之间分配负载。要向缓存池中添加内存,只需添加另一个 Memcached 主机即可。
由于 Memcached 专为缓存而设计,因此它的架构复杂性不如用于扩展工作负载的持久性解决方案。在考虑更复杂的解决方案之前,请考虑缓存是否可以解决您的可伸缩性问题。缓存不仅仅是一个临时的创可贴:Facebook 在数十 TB 的内存范围内安装了 Memcached!
读取副本和缓存允许您扩展读取密集型工作负载。但是,当您开始增加写入和更新数据的频率时,您也会增加包含所有最新数据的主服务器上的负载。对于本节的其余部分,我们将介绍跨多个服务器分片写入工作负载的技术。

Do Not Shard Until You Have To

Sharding adds system complexity, and where possible, you should avoid it. Let's cover two ways to scale without sharding: read replicas and caching.

Read Replicas

Many storage systems see more read requests than write requests. A simple solution in these cases is to make copies of the data on multiple machines. All write requests still go to a master node. Read requests go to machines which replicate the data, and are often slightly stale with respect to the data on the write master.

If you are already replicating your data for multi-server durability in a master-slave configuration, as is common in Redis, CouchDB, or MongoDB, the read slaves can shed some load from the write master. Some queries, such as aggregate summaries of your dataset, which might be expensive and often do not require up-to-the-second freshness, can be executed against the slave replicas. Generally, the less stringent your demands for freshness of content, the more you can lean on read slaves to improve read-only query performance.

Caching

Caching the most popular content in your system often works surprisingly well. Memcached dedicates blocks of memory on multiple servers to cache data from your data store. Memcached clients take advantage of several horizontal scalability tricks to distribute load across Memcached installations on different servers. To add memory to the cache pool, just add another Memcached host.

Because Memcached is designed for caching, it does not have as much architectural complexity as the persistent solutions for scaling workloads. Before considering more complicated solutions, think about whether caching can solve your scalability woes. Caching is not solely a temporary band-aid: Facebook has Memcached installations in the range of tens of terabytes of memory!

Read replicas and caching allow you to scale up your read-heavy workloads. When you start to increase the frequency of writes and updates to your data, however, you will also increase the load on the master server that contains all of your up-to-date data. For the rest of this section, we will cover techniques for sharding your write workload across multiple servers.


通过协调器分片

CouchDB 项目专注于单服务器体验。Lounge 和 BigCouch 这两个项目通过外部代理促进 CouchDB 工作负载的分片,外部代理充当独立 CouchDB 实例的前端。在此设计中,独立安装相互不了解。协调器根据所请求文档的键将请求分发到各个 CouchDB 实例。
Twitter 已将分片和复制的概念构建到一个名为 Gizzard 16的协调框架中。Gizzard 采用任何类型的独立数据存储——您可以为 SQL 或 NoSQL 存储系统构建包装器——并将它们排列在任意深度的树中,以按键范围对键进行分区。对于容错,Gizzard 可以配置为将数据复制到同一键范围的多个物理机器。

Sharding Through Coordinators

The CouchDB project focuses on the single-server experience. Two projects, Lounge and BigCouch, facilitate sharding CouchDB workloads through an external proxy, which acts as a front end to standalone CouchDB instances. In this design, the standalone installations are not aware of each other. The coordinator distributes requests to individual CouchDB instances based on the key of the document being requested.

Twitter has built the notions of sharding and replication into a coordinating framework called Gizzard16 . Gizzard takes standalone data stores of any type—you can build wrappers for SQL or NoSQL storage systems—and arranges them in trees of any depth to partition keys by key range. For fault tolerance, Gizzard can be configured to replicate data to multiple physical machines for the same key range.


一致性哈希环

好的散列函数以统一的方式分配一组密钥。这使它们成为在多个服务器之间分发键值对的强大工具。关于一种称为一致性哈希的技术的学术文献非常广泛,并且该技术在数据存储中的首次应用是在称为 分布式哈希表DHT ) 的系统中。围绕 Amazon 的 Dynamo 原理构建的 NoSQL 系统采用了这种分布技术,它出现在 Cassandra、Voldemort 和 Riak 中。
Good hash functions distribute a set of keys in a uniform manner. This makes them a powerful tool for distributing key-value pairs among multiple servers. The academic literature on a technique called consistent hashing is extensive, and the first applications of the technique to data stores was in systems called distributed hash tables (DHTs). NoSQL systems built around the principles of Amazon's Dynamo adopted this distribution technique, and it appears in Cassandra, Voldemort, and Riak.

哈希环实例 Hash Rings by Example

图1:分布式哈希表环
一致性哈希环的工作原理如下。假设我们有一个哈希函数 H,可以将键映射到均匀分布的大整数值。我们可以通过对某个相对较大的整数 L 取 H(key) mod L 来形成 [1, L] 范围内的数字环,并用这些值环绕自身。这会将每个键映射到 [1, L] 范围内. 一个一致的服务器散列环是通过获取每个服务器的唯一标识符(比如它的 IP 地址)并对其应用 H 来形成的。通过查看图 1 A中由五个服务器 ( - E)形成的散列环,您可以直观地了解其工作原理。
在那里,我们选择了L = 1000. 假设H(A) mod L = 7H(B) mod L = 234H(C) mod L = 447,H(D) mod L = 660H(E) mod L = 875。我们现在可以知道密钥应该存在于哪个服务器上。为此,我们通过查看它是否落在该服务器和环中下一个服务器之间的范围内,将所有密钥映射到服务器。例如,A负责散列值在 [7,233] 范围内的键,并E负责 [875, 6] 范围内的键(该范围在 1000 处自我回绕)。因此,如果H('employee30') mod L = 899,它将由服务器存储E,如果H('employee31') mod L = 234,它将存储在服务器上B

复制数据

多服务器持久性复制是通过将一个服务器分配范围内的键和值传递给环中它后面的服务器来实现的。例如,复制因子为 3,映射到范围 [7,233] 的键将存储在服务器 AB和上C。如果发生故障,A它的邻居将接管它的工作量。在某些设计中, 会临时复制并接管' 的工作负载,因为它的范围会扩展到包含' 的。BCEAA

实现更好的分配

虽然哈希在均匀分布密钥空间方面在统计上是有效的,但它通常需要很多服务器才能均匀分布。不幸的是,我们通常从一小部分服务器开始,这些服务器之间并没有完全通过散列函数分开。在我们的示例中,A的键范围长度为 227,而E的范围为 132。这会导致不同服务器上的负载不均。这也使得服务器在发生故障时很难相互接管,因为邻居突然不得不控制发生故障的服务器的整个范围。
为了解决大密钥范围不均匀的问题,包括 Riak 在内的许多 DHT 在每台物理机器上创建了多个“虚拟”节点。例如,对于 4 个虚拟节点,服务器A将充当服务器 A_1A_2A_3A_4。每个虚拟节点散列为不同的值,使其有更多机会管理分布到密钥空间不同部分的密钥。Voldemort 采用了类似的方法,其中分区的数量是手动配置的,并且通常大于服务器的数量,导致每个服务器接收许多较小的分区。
Cassandra 不会为每个服务器分配多个小分区,导致有时会出现键范围分布不均匀的情况。对于负载均衡,Cassandra 有一个异步进程,它根据历史负载调整服务器在环上的位置。

Consistent hash rings work as follows. Say we have a hash function H that maps keys to uniformly distributed large integer values. We can form a ring of numbers in the range [1, L] that wraps around itself with these values by taking H(key) mod L for some relatively large integer L. This will map each key into the range [1,L]. A consistent hash ring of servers is formed by taking each server's unique identifier (say its IP address), and applying H to it. You can get an intuition for how this works by looking at the hash ring formed by five servers (A -E) in Figure 13.1.

There, we picked L = 1000. Let's say that H(A) mod L = 7H(B) mod L = 234H(C) mod L = 447H(D) mod L = 660, and H(E) mod L = 875. We can now tell which server a key should live on. To do this, we map all keys to a server by seeing if it falls in the range between that server and the next one in the ring. For example, A is responsible for keys whose hash value falls in the range [7,233], and E is responsible for keys in the range [875, 6] (this range wraps around on itself at 1000). So if H('employee30') mod L = 899, it will be stored by server E, and if H('employee31') mod L = 234, it will be stored on server B.

Replicating Data

Replication for multi-server durability is achieved by passing the keys and values in one server's assigned range to the servers following it in the ring. For example, with a replication factor of 3, keys mapped to the range [7,233] will be stored on servers AB, and C. If A were to fail, its neighbors B and C would take over its workload. In some designs, E would replicate and take over A's workload temporarily, since its range would expand to include A's.

Achieving Better Distribution

While hashing is statistically effective at uniformly distributing a keyspace, it usually requires many servers before it distributes evenly. Unfortunately, we often start with a small number of servers that are not perfectly spaced apart from one-another by the hash function. In our example, A's key range is of length 227, whereas E's range is 132. This leads to uneven load on different servers. It also makes it difficult for servers to take over for one-another when they fail, since a neighbor suddenly has to take control of the entire range of the failed server.

To solve the problem of uneven large key ranges, many DHTs including Riak create several `virtual' nodes per physical machine. For example, with 4 virtual nodes, server A will act as server A_1A_2A_3, and A_4. Each virtual node hashes to a different value, giving it more opportunity to manage keys distributed to different parts of the keyspace. Voldemort takes a similar approach, in which the number of partitions is manually configured and usually larger than the number of servers, resulting in each server receiving a number of smaller partitions.

Cassandra does not assign multiple small partitions to each server, resulting in sometimes uneven key range distributions. For load-balancing, Cassandra has an asynchronous process which adjusts the location of servers on the ring depending on their historic load.


范围分区

在分片的范围分区方法中,系统中的某些机器保留有关哪些服务器包含哪些键范围的元数据。查阅此元数据以将键和范围查找路由到适当的服务器
与一致性哈希环方法一样,此范围分区将键空间拆分为多个范围,每个键范围由一台机器管理并可能复制到其他机器。
与一致性哈希方法不同,在键的排序顺序中彼此相邻的两个键很可能出现在同一分区中。这减少了路由元数据的大小,因为大范围被压缩到 [start, end] 标记。
在添加范围到服务器映射的活动记录保存时,范围分区方法允许更细粒度地控制从重载服务器卸载负载。如果特定键范围的流量高于其他范围,负载管理器可以减小该服务器上范围的大小,或减少该服务器服务的分片数量。主动管理负载的额外自由是以监控和路由分片的额外架构组件为代价的。

Range Partitioning

In the range partitioning approach to sharding, some machines in your system keep metadata about which servers contain which key ranges. This metadata is consulted to route key and range lookups to the appropriate servers. 

Like the consistent hash ring approach, this range partitioning splits the keyspace into ranges, with each key range being managed by one machine and potentially replicated to others. 

Unlike the consistent hashing approach, two keys that are next to each other in the key's sort order are likely to appear in the same partition. This reduces the size of the routing metadata, as large ranges are compressed to [start, end] markers.

In adding active record-keeping of the range-to-server mapping, the range partitioning approach allows for more fine-grained control of load-shedding from heavily loaded servers. If a specific key range sees higher traffic than other ranges, a load manager can reduce the size of the range on that server, or reduce the number of shards that this server serves. The added freedom to actively manage load comes at the expense of extra architectural components which monitor and route shards.


BigTable 方式

Google 的 BigTable 论文描述了一种范围分区分层技术,用于将数据分片到Tablet。Tablet 在列族中存储一系列行键和值。它维护所有必要的日志和数据结构,以回答有关其分配范围内的键的查询。Tablet 服务器根据每个Tablet所承受的负载为多个Tablet提供服务。
每个Tablet的大小保持在 100-200 MB。随着 tablet 大小的变化,两个具有相邻键范围的小 tablet 可能会合并,或者一个大 tablet 可能会一分为二。主服务器分析Tablet的大小、负载和Tablet服务器的可用性。master随时调整哪个tablet server服务于哪个tablets。

The BigTable Way

Google's BigTable paper describes a range-partitioning hierarchical technique for sharding data into tablets. A tablet stores a range of row keys and values within a column family. It maintains all of the necessary logs and data structures to answer queries about the keys in its assigned range. Tablet servers serve multiple tablets depending on the load each tablet is experiencing.

Each tablet is kept at a size of 100-200 MB. As tablets change in size, two small tablets with adjoining key ranges might be combined, or a large tablet might be split in two. A master server analyzes tablet size, load, and tablet server availability. The master adjusts which tablet server serves which tablets at any time.


图2:基于 BigTable 的范围分区

主服务器在元数据表中维护 tablet 分配。因为这个元数据会变得很大,所以元数据表也被分片成片,将键范围映射到片和负责这些范围的片服务器。这导致客户端进行三层层次结构遍历以在其托管Tablet服务器上找到密钥,如图2 所示
让我们看一个例子。搜索密钥的客户端 900将查询服务器A,该服务器存储元数据级别 0 的Tablet。该Tablet识别服务器 6 上的元数据级别 1 Tablet,包含键范围 500-1500。客户端用这个键向服务器发送请求,服务器B响应在服务器 C 上的平板上找到包含键 850-950 的Tablet。最后,客户端向服务器发送键请求C,并取回行数据以供其查询. 0 级和 1 级的元数据 tablet 可以由客户端缓存,这避免了重复查询给他们的 tablet 服务器带来不必要的负载。BigTable 论文解释说,这个 3 级层次结构可以使用 128MB 的Tablet容纳 2 个61字节的存储空间。

The master server maintains the tablet assignment in a metadata table. Because this metadata can get large, the metadata table is also sharded into tablets that map key ranges to tablets and tablet servers responsible for those ranges. This results in a three-layer hierarchy traversal for clients to find a key on its hosting tablet server, as depicted in Figure 13.2.

Let's look at an example. A client searching for key 900 will query server A, which stores the tablet for metadata level 0. This tablet identifies the metadata level 1 tablet on server 6 containing key ranges 500-1500. The client sends a request to server B with this key, which responds that the tablet containing keys 850-950 is found on a tablet on server C. Finally, the client sends the key request to server C, and gets the row data back for its query. Metadata tablets at level 0 and 1 may be cached by the client, which avoids putting undue load on their tablet servers from repeat queries. The BigTable paper explains that this 3-level hierarchy can accommodate 261 bytes worth of storage using 128MB tablets.


处理失败

master 是 BigTable 设计中的单点故障,但可以暂时关闭而不影响对 tablet 服务器的请求。如果 tablet 服务器在处理 tablet 请求时出现故障,则由 master 识别并在请求暂时失败时重新分配其 tablets。
为了识别和处理机器故障,BigTable 论文描述了 Chubby 的使用,Chubby 是一种用于管理服务器成员资格和活跃度的分布式锁定系统。ZooKeeper 17是 Chubby 的开源实现,多个基于 Hadoop 的项目利用它来管理辅助主服务器和平板服务器重新分配。

Handling Failures

The master is a single point of failure in the BigTable design, but can go down temporarily without affecting requests to tablet servers. If a tablet server fails while serving tablet requests, it is up to the master to recognize this and re-assign its tablets while requests temporarily fail.

In order to recognize and handle machine failures, the BigTable paper describes the use of Chubby, a distributed locking system for managing server membership and liveness. ZooKeeper17 is the open source implementation of Chubby, and several Hadoop-based projects utilize it to manage secondary master servers and tablet server reassignment.


基于范围分区的 NoSQL 项目


HBase 采用 BigTable 的分层方法进行范围分区。底层 tablet 数据存储在 Hadoop 的分布式文件系统 (HDFS) 中。HDFS 处理副本之间的数据复制和一致性,让 tablet 服务器处理请求、更新存储结构以及启动 tablet 拆分和压缩。
MongoDB 以类似于 BigTable 的方式处理范围分区。几个配置节点存储和管理指定哪个存储节点负责哪个键范围的路由表这些配置节点通过称为两阶段提交的协议保持同步,并作为 BigTable 的 master 指定范围和 Chubby 的混合体用于高可用性配置管理。独立的无状态路由进程跟踪最新的路由配置并将密钥请求路由到适当的存储节点。存储节点排列在副本集中以处理复制。
如果您希望对数据进行快速范围扫描,Cassandra 会提供一个保序分区程序。Cassandra 节点仍然使用一致性哈希排列在环中,但不是将键值对散列到环上以确定应将其分配到的服务器,而是将键简单地映射到控制其范围的服务器上钥匙自然适合。例如,密钥 20 和 21 都将映射到图 13.1中一致哈希环中的服务器 A ,而不是在环中进行哈希和随机分布。
Twitter 用于跨多个后端管理分区和复制数据的 Gizzard 框架使用范围分区来分片数据。路由服务器形成任意深度的层次结构,将键范围分配给层次结构中位于它们之下的服务器。这些服务器要么在其指定范围内存储密钥数据,要么路由到另一层路由服务器。此模型中的复制是通过向多台机器发送关键范围的更新来实现的。Gizzard 路由节点以不同于其他 NoSQL 系统的方式管理失败的写入。Gizzard 要求系统设计者使所有更新都是幂等的(它们可以运行两次)。当存储节点发生故障时,路由节点缓存并重复向该节点发送更新,直到更新被确认。


Range Partitioning-based NoSQL Projects


HBase employs BigTable's hierarchical approach to range-partitioning. Underlying tablet data is stored in Hadoop's distributed filesystem (HDFS). HDFS handles data replication and consistency among replicas, leaving tablet servers to handle requests, update storage structures, and initiate tablet splits and compactions.

MongoDB handles range partitioning in a manner similar to that of BigTable. Several configuration nodes store and manage the routing tables that specify which storage node is responsible for which key ranges. These configuration nodes stay in sync through a protocol called two-phase commit, and serve as a hybrid of BigTable's master for specifying ranges and Chubby for highly available configuration management. Separate routing processes, which are stateless, keep track of the most recent routing configuration and route key requests to the appropriate storage nodes. Storage nodes are arranged in replica sets to handle replication.

Cassandra provides an order-preserving partitioner if you wish to allow fast range scans over your data. Cassandra nodes are still arranged in a ring using consistent hashing, but rather than hashing a key-value pair onto the ring to determine the server to which it should be assigned, the key is simply mapped onto the server which controls the range in which the key naturally fits. For example, keys 20 and 21 would both be mapped to server A in our consistent hash ring in Figure 13.1, rather than being hashed and randomly distributed in the ring.

Twitter's Gizzard framework for managing partitioned and replicated data across many back ends uses range partitioning to shard data. Routing servers form hierarchies of any depth, assigning ranges of keys to servers below them in the hierarchy. These servers either store data for keys in their assigned range, or route to yet another layer of routing servers. Replication in this model is achieved by sending updates to multiple machines for a key range. Gizzard routing nodes manage failed writes in different manner than other NoSQL systems. Gizzard requires that system designers make all updates idempotent (they can be run twice). When a storage node fails, routing nodes cache and repeatedly send updates to the node until the update is confirmed.


使用哪种分区方案

鉴于基于散列和范围的分片方法,哪种方法更可取?这取决于。当您经常对数据键执行范围扫描时,范围分区是显而易见的选择。当您按键顺序读取值时,您不会跳转到网络中的随机节点,这会产生沉重的网络开销。但是,如果您不需要范围扫描,您应该使用哪种分片方案?
哈希分区使数据在节点之间合理分布,并且可以通过虚拟节点减少随机偏斜。散列分区方案中的路由很简单:在大多数情况下,客户端可以执行散列函数来找到合适的服务器。随着更复杂的再平衡方案,为密钥找到正确的节点变得更加困难。
范围分区需要维护路由和配置节点的前期成本,在没有相对复杂的容错方案的情况下,这些节点可能会承受沉重的负载并成为故障的中心点。然而,做得好,范围分区的数据可以在小块中进行负载平衡,这些小块可以在高负载情况下重新分配。如果一台服务器出现故障,其分配的范围可以分配给许多服务器,而不是在停机期间加载服务器的直接邻居。

Which Partitioning Scheme to Use

Given the hash- and range-based approaches to sharding, which is preferable? It depends. Range partitioning is the obvious choice to use when you will frequently be performing range scans over the keys of your data. As you read values in order by key, you will not jump to random nodes in the network, which would incur heavy network overhead. But if you do not require range scans, which sharding scheme should you use?

Hash partitioning gives reasonable distribution of data across nodes, and random skew can be reduced with virtual nodes. Routing is simple in the hash partitioning scheme: for the most part, the hash function can be executed by clients to find the appropriate server. With more complicated rebalancing schemes, finding the right node for a key becomes more difficult.

Range partitioning requires the upfront cost of maintaining routing and configuration nodes, which can see heavy load and become central points of failure in the absence of relatively complex fault tolerance schemes. Done well, however, range-partitioned data can be load-balanced in small chunks which can be reassigned in high-load situations. If a server goes down, its assigned ranges can be distributed to many servers, rather than loading the server's immediate neighbors during downtime.



一致性

谈到将数据复制到多台机器以实现持久性和分散负载的优点之后,是时候告诉您一个秘密了:让多台机器上的数据副本彼此保持一致是很困难的。在实践中,副本会崩溃并失去同步,副本会崩溃并且永远不会恢复,网络会分割两组副本,机器之间的消息会延迟或丢失。NoSQL 生态系统中有两种主要的数据一致性方法。第一个是强一致性,所有副本都保持同步。第二种是最终一致性,允许副本不同步,但最终会相互追上。让我们首先通过了解分布式计算的基本属性来了解为什么第二种选择是适当的考虑因素。

Consistency

Having spoken about the virtues of replicating data to multiple machines for durability and spreading load, it's time to let you in on a secret: keeping replicas of your data on multiple machines consistent with one-another is hard. In practice, replicas will crash and get out of sync, replicas will crash and never come back, networks will partition two sets of replicas, and messages between machines will get delayed or lost. There are two major approaches to data consistency in the NoSQL ecosystem. The first is strong consistency, where all replicas remain in sync. The second is eventual consistency, where replicas are allowed to get out of sync, but eventually catch up with one-another. Let's first get into why the second option is an appropriate consideration by understanding a fundamental property of distributed computing. After that, we'll jump into the details of each approach.

关于CAP

为什么我们要考虑对我们的数据进行强一致性保证之外的任何事情?这一切都归结为为现代网络设备设计的分布式系统的一个特性。这个想法首先由 Eric Brewer 作为CAP 定理提出,后来由 Gilbert 和 Lynch [ GL02 ] 证明。该定理首先提出了分布式系统的三个属性,它们构成了首字母缩略词 CAP:
  • 一致性:当您读取一条数据时,它的所有副本是否总是在逻辑上同意该数据的同一版本?(这种一致性的概念不同于 ACID 中的 C。)
  • 可用性:无论有多少副本不可访问,副本是否响应读写请求?
  • Partition tolerance:即使某些副本暂时失去了网络通信能力,系统还能继续运行吗?
然后该定理继续说,在多台计算机上运行的存储系统只能以牺牲第三个为代价来实现其中两个属性。此外,我们被迫实施分区容错系统。在使用当前消息传递协议的当前网络硬件上,数据包可能会丢失,交换机可能会发生故障,并且无法知道网络是否已关闭或您尝试向其发送消息的服务器不可用。所有 NoSQL 系统都应该是分区容错的。剩下的选择是在一致性和可用性之间。没有任何 NoSQL 系统可以同时提供两者。
选择一致性意味着您复制的数据不会在副本之间不同步。实现一致性的一种简单方法是要求所有副本确认更新。如果副本出现故障并且您无法确认其上的数据更新,那么您会降低其密钥的可用性。这意味着在所有副本恢复并响应之前,用户无法收到对其更新操作的成功确认。因此,选择一致性就是选择缺少每个数据项的全天候可用性。
选择可用性意味着当用户发出操作时,副本应该对它们拥有的数据进行操作,而不管其他副本的状态如何。这可能会导致副本之间的数据一致性不同,因为它们不需要确认所有更新,并且某些副本可能没有注意到所有更新。
CAP 定理的含义导致构建 NoSQL 数据存储的强一致性和最终一致性方法。还存在其他方法,例如 Yahoo! 的 PNUTS [ CRS+08 ] 系统中提出的宽松一致性和宽松可用性方法。我们讨论的开源 NoSQL 系统还没有采用这种技术,所以我们不会进一步讨论它。

A Little Bit About CAP

Why are we considering anything short of strong consistency guarantees over our data? It all comes down to a property of distributed systems architected for modern networking equipment. The idea was first proposed by Eric Brewer as the CAP Theorem, and later proved by Gilbert and Lynch [GL02]. The theorem first presents three properties of distributed systems which make up the acronym CAP:

  • Consistency: do all replicas of a piece of data always logically agree on the same version of that data by the time you read it? (This concept of consistency is different than the C in ACID.)

  • Availability: Do replicas respond to read and write requests regardless of how many replicas are inaccessible?

  • Partition tolerance: Can the system continue to operate even if some replicas temporarily lose the ability to communicate with each other over the network?

The theorem then goes on to say that a storage system which operates on multiple computers can only achieve two of these properties at the expense of a third. Also, we are forced to implement partition-tolerant systems. On current networking hardware using current messaging protocols, packets can be lost, switches can fail, and there is no way to know whether the network is down or the server you are trying to send a message to is unavailable. All NoSQL systems should be partition-tolerant. The remaining choice is between consistency and availability. No NoSQL system can provide both at the same time.

Opting for consistency means that your replicated data will not be out of sync across replicas. An easy way to achieve consistency is to require that all replicas acknowledge updates. If a replica goes down and you can not confirm data updates on it, then you degrade availability on its keys. This means that until all replicas recover and respond, the user can not receive successful acknowledgment of their update operation. Thus, opting for consistency is opting for a lack of round-the-clock availability for each data item.

Opting for availability means that when a user issues an operation, replicas should act on the data they have, regardless of the state of other replicas. This may lead to diverging consistency of data across replicas, since they weren't required to acknowledge all updates, and some replicas may have not noted all updates.

The implications of the CAP theorem lead to the strong consistency and eventual consistency approaches to building NoSQL data stores. Other approaches exist, such as the relaxed consistency and relaxed availability approach presented in Yahoo!'s PNUTS [CRS+08] system. None of the open source NoSQL systems we discuss has adopted this technique yet, so we will not discuss it further.


强一致性

促进强一致性的系统确保数据项的副本始终能够就密钥的值达成共识。一些副本可能彼此不同步,但当用户询问 的值时employee30:salary,机器有办法一致同意用户看到的值。这是如何工作的最好用数字来解释。
假设我们在 N 台机器上复制一个密钥。一些机器,也许是其中之一N,充当每个用户请求的协调器。协调器确保一定数量的N机器已收到并确认每个请求。当对密钥进行写入或更新时,协调器不会向用户确认写入已发生,直到W副本确认它们已收到更新。当用户想要读取某个键的值时,协调器会在至少 R 已响应相同的值时做出响应。如果 ,我们说系统体现了强一致性 R+W>N
给这个想法加上一些数字,假设我们正在 N=3 台机器上复制每个密钥(称它们A为 、B和 C)。假设密钥employee30:salary最初设置为 $20,000 的值,但我们想employee30加薪到 $30,000。让我们要求至少W=2A或 确认每个密钥写入请求BC当 AB确认 的写入请求时(employee30:salary, $30,000),协调器让用户知道 employee30:salary已安全更新。假设机器 C 从未收到对 的写请求employee30:salary,因此它的值仍然为 $20,000。当协调器收到 key 的读取请求时employee30:salary,它将将该请求发送到所有 3 台机器:
  • 如果我们设置R=1,并且机器C首先响应 20,000 美元,我们的员工将不会很高兴。
  • 但是,如果我们设置R=2,协调器将看到来自 的值C,等待来自A或 的第二次响应B ,这将与C的过时值发生冲突,最后收到来自第三台机器的响应,这将确认 $30,000 是多数意见.
所以为了在这种情况下实现强一致性,我们需要设置 R=2}使得R+W3}。
W副本不响应写入请求,或者R副本不以一致的响应响应读取请求时会发生什么?协调器最终可能会超时并向用户发送错误,或者等到情况自行纠正。无论哪种方式,系统都被认为至少在一段时间内对该请求不可用。
您的选择RW影响在您的系统对一个键的不同操作变得不可用之前有多少台机器可以做出奇怪的行为。例如,如果您强制所有副本都确认写入,那么W=N,写入操作将挂起或因任何副本故障而失败。一个常见的选择是R + W = N + 1,强一致性所需的最低要求,同时仍然允许副本之间的临时分歧。许多强一致性系统选择W=NR=1,因为它们不必为不同步的节点进行设计。
HBase 将其复制存储基于分布式存储层 HDFS。HDFS 提供强大的一致性保证。N在 HDFS 中,写入只有在复制到所有(通常是 2 或 3 个)副本后才能成功,因此W = N. 读取将由单个副本满足,因此R = 1. 为了避免陷入写密集型工作负载的泥潭,数据从用户异步并行传输到副本。一旦所有副本确认它们已收到数据副本,将新数据交换到系统的最后一步将在所有副本上自动且一致地执行。

Strong Consistency

Systems which promote strong consistency ensure that the replicas of a data item will always be able to come to consensus on the value of a key. Some replicas may be out of sync with one-another, but when the user asks for the value of employee30:salary, the machines have a way to consistently agree on the value the user sees. How this works is best explained with numbers.

Say we replicate a key on N machines. Some machine, perhaps one of the N, serves as a coordinator for each user request. The coordinator ensures that a certain number of the N machines has received and acknowledged each request. When a write or update occurs to a key, the coordinator does not confirm with the user that the write occurred until W replicas confirm that they have received the update. When a user wants to read the value for some key, the coordinator responds when at least R have responded with the same value. We say that the system exemplifies strong consistency if R+W>N.

Putting some numbers to this idea, let's say that we're replicating each key across N=3 machines (call them AB, and C). Say that the key employee30:salary is initially set to the value $20,000, but we want to give employee30 a raise to $30,000. Let's require that at least W=2 of AB, or C acknowledge each write request for a key. When A and B confirm the write request for (employee30:salary, $30,000), the coordinator lets the user know that employee30:salary is safely updated. Let's assume that machine C never received the write request for employee30:salary, so it still has the value $20,000. When a coordinator gets a read request for key employee30:salary, it will send that request to all 3 machines:

  • If we set R=1, and machine C responds first with $20,000, our employee will not be very happy.

  • However, if we set R=2, the coordinator will see the value from C, wait for a second response from A or B, which will conflict with C's outdated value, and finally receive a response from the third machine, which will confirm that $30,000 is the majority opinion.

So in order to achieve strong consistency in this case, we need to set R=2} so that R+W3}.

What happens when W replicas do not respond to a write request, or R replicas do not respond to a read request with a consistent response? The coordinator can timeout eventually and send the user an error, or wait until the situation corrects itself. Either way, the system is considered unavailable for that request for at least some time.

Your choice of R and W  affect how many machines can act strangely before your system becomes unavailable for different actions on a key. If you force all of your replicas to acknowledge writes, for example, then W=N, and write operations will hang or fail on any replica failure. A common choice is R + W = N + 1, the minimum required for strong consistency while still allowing for temporary disagreement between replicas. Many strong consistency systems opt for W=N and R=1, since they then do not have to design for nodes going out of sync.

HBase bases its replicated storage on HDFS, a distributed storage layer. HDFS provides strong consistency guarantees. In HDFS, a write cannot succeed until it has been replicated to all N (usually 2 or 3) replicas, so W = N. A read will be satisfied by a single replica, so R = 1. To avoid bogging down write-intensive workloads, data is transferred from the user to the replicas asynchronously in parallel. Once all replicas acknowledge that they have received copies of the data, the final step of swapping the new data in to the system is performed atomically and consistently across all replicas.


 最终一致性

基于 Dynamo 的系统,包括 Voldemort、Cassandra 和 Riak,允许用户指定NRW他们的需求,即使R + W <= N. 这意味着用户可以实现强一致性或最终一致性。当用户选择最终一致性时,甚至当程序员选择强一致性但W小于时,有时N副本可能不会一致。为了提供副本之间的最终一致性,这些系统采用各种工具来加快陈旧副本的速度。我们首先介绍各种系统如何确定数据不同步,然后讨论它们如何同步副本,最后引入一些受发电机启发的方法来加速同步过程。

Eventual Consistency

Dynamo-based systems, which include Voldemort, Cassandra, and Riak, allow the user to specify NR, and W to their needs, even if R + W <= N. This means that the user can achieve either strong or eventual consistency. When a user picks eventual consistency, and even when the programmer opts for strong consistency but W is less than N, there are periods in which replicas might not see eye-to-eye. To provide eventual consistency among replicas, these systems employ various tools to catch stale replicas up to speed. Let's first cover how various systems determine that data has gotten out of sync, then discuss how they synchronize replicas, and finally bring in a few dynamo-inspired methods for speeding up the synchronization process.


版本控制和冲突

因为两个副本可能会看到某个键值的两个不同版本,所以数据版本控制和冲突检测很重要。基于发电机的系统使用一种称为矢量时钟的版本控制。矢量时钟是分配给每个键的矢量,其中包含每个副本的计数器。例如,如果服务器ABC是某个键的三个副本,则矢量时钟将具有三个条目 ,(N_A, N_B, N_C), 初始化为(0,0,0)
每次副本修改密钥时,它都会增加向量中的计数器。如果 B 修改了以前有版本的密钥(39, 1, 5),它将把矢量时钟更改为(39, 2, 5). 当另一个副本,比如C,从 B 接收到关于密钥数据的更新时,它会将向量时钟与B它自己的进行比较。只要它自己的矢量时钟计数器都小于从中传递的那些,那么它就有一个陈旧的版本并且可以用'B覆盖它自己的副本。B如果BC有时钟,其中一些计数器在两个时钟中都大于其他计数器,比如 (39, 2, 5)(39, 1, 6),那么服务器会识别出它们随着时间的推移收到了不同的、可能不可调和的更新,并识别出冲突。

Versioning and Conflicts

Because two replicas might see two different versions of a value for some key, data versioning and conflict detection is important. The dynamo-based systems use a type of versioning called vector clocks. A vector clock is a vector assigned to each key which contains a counter for each replica. For example, if servers AB, and C are the three replicas of some key, the vector clock will have three entries, (N_A, N_B, N_C), initialized to  (0,0,0).

Each time a replica modifies a key, it increments its counter in the vector. If B modifies a key that previously had version (39, 1, 5), it will change the vector clock to (39, 2, 5). When another replica, say C, receives an update from B about the key's data, it will compare the vector clock from B to its own. As long as its own vector clock counters are all less than the ones delivered from B, then it has a stale version and can overwrite its own copy with B's. If B and C have clocks in which some counters are greater than others in both clocks, say (39, 2, 5) and (39, 1, 6), then the servers recognize that they received different, potentially unreconcilable updates over time, and identify a conflict.


解决冲突

不同系统的冲突解决方案各不相同。Dynamo 论文将冲突解决留给了使用存储系统的应用程序。购物车的两个版本可以合并为一个而不会丢失大量数据,但是协作编辑的文档的两个版本可能需要人工审阅才能解决冲突。Voldemort 遵循此模型,在发生冲突时将密钥的多个副本返回给请求的客户端应用程序。
Cassandra 在每个密钥上存储时间戳,当两个版本发生冲突时,它会使用密钥的最近时间戳版本。这消除了往返客户端的需要并简化了 API。这种设计很难处理可以智能合并冲突数据的情况,例如在我们的购物车示例中,或者在实现分布式计数器时。Riak 允许 Voldemort 和 Cassandra 提供的两种方法。CouchDB 提供了一种混合方式:它识别冲突并允许用户查询冲突的键以进行手动修复,但确定性地选择一个版本返回给用户,直到冲突被修复。

Conflict Resolution

Conflict resolution varies across the different systems. The Dynamo paper leaves conflict resolution to the application using the storage system. Two versions of a shopping cart can be merged into one without significant loss of data, but two versions of a collaboratively edited document might require human reviewer to resolve conflict. Voldemort follows this model, returning multiple copies of a key to the requesting client application upon conflict.

Cassandra, which stores a timestamp on each key, uses the most recently timestamped version of a key when two versions are in conflict. This removes the need for a round-trip to the client and simplifies the API. This design makes it difficult to handle situations where conflicted data can be intelligently merged, as in our shopping cart example, or when implementing distributed counters. Riak allows both of the approaches offered by Voldemort and Cassandra. CouchDB provides a hybrid: it identifies a conflict and allows users to query for conflicted keys for manual repair, but deterministically picks a version to return to users until conflicts are repaired.



读取修复

如果 R 副本将非冲突数据返回给协调器,则协调器可以安全地将非冲突数据返回给应用程序。协调器可能仍然注意到一些副本不同步。Dynamo 论文建议,Cassandra、Riak 和 Voldemort 实施了一种称为读取修复的技术 来处理此类情况。当协调器识别出读取冲突时,即使已向用户返回一致的值,协调器也会在冲突的副本之间启动冲突解决协议。这会主动修复冲突,几乎不需要额外的工作。副本已经将它们的数据版本发送给协调器,更快的冲突解决将减少系统中的分歧。

Read Repair

If R replicas return non-conflicting data to a coordinator, the coordinator can safely return the non-conflicting data to the application. The coordinator may still notice that some of the replicas are out of sync. The Dynamo paper suggests, and Cassandra, Riak, and Voldemort implement, a technique called read repair for handling such situations. When a coordinator identifies a conflict on read, even if a consistent value has been returned to the user, the coordinator starts conflict-resolution protocols between conflicted replicas. This proactively fixes conflicts with little additional work. Replicas have already sent their version of the data to the coordinator, and faster conflict resolution will result in less divergence in the system.



提示切换

Cassandra、Riak 和 Voldemort 都采用了一种称为 暗示切换的技术在节点暂时不可用的情况下提高写入性能。如果某个键的副本之一没有响应写入请求,则选择另一个节点暂时接管其写入工作负载。不可用节点的写入是分开保存的,当备份节点注意到以前不可用的节点可用时,它会将所有写入转发到新的可用副本。Dynamo论文使用了一种“草率的法定人数”方法,并允许通过暗示切换完成的写入计入W所需的写入确认。Cassandra和Voldemort不会计入针对W的暗示切换,并且会导致没有来自最初分配的副本的W确认的写入失败。提示切换在这些系统中仍然有用。

Hinted Handoff

Cassandra, Riak, and Voldemort all employ a technique called hinted handoff to improve write performance for situations where a node temporarily becomes unavailable. If one of the replicas for a key does not respond to a write request, another node is selected to temporarily take over its write workload. Writes for the unavailable node are kept separately, and when the backup node notices the previously unavailable node become available, it forwards all of the writes to the newly available replica. The Dynamo paper utilizes a 'sloppy quorum' approach and allows the writes accomplished through hinted handoff to count toward the W required write acknowledgments. Cassandra and Voldemort will not count a hinted handoff against W, and will fail a write which does not have W confirmations from the originally assigned replicas. Hinted handoff is still useful in these systems, as it speeds up recovery when an unavailable node returns.


反熵

当副本停机时间过长,或者存储不可用副本的提示切换的机器也停机时,副本必须相互同步。在这种情况下,Cassandra 和 Riak 实施了一个名为 anti-entropy的受 Dynamo 启发的过程。在反熵中,副本交换Merkle 树以识别其复制的键范围中不同步的部分。Merkle树是一种分层哈希验证:如果整个密钥空间的哈希值在两个副本之间不相同,它们将交换复制密钥空间的越来越小部分的哈希值,直到识别出不同步的密钥。这种方法减少了包含大部分相似数据的副本之间不必要的数据传输。

Anti-Entropy

When a replica is down for an extended period of time, or the machine storing hinted handoffs for an unavailable replica goes down as well, replicas must synchronize from one-another. In this case, Cassandra and Riak implement a Dynamo-inspired process called anti-entropy. In anti-entropy, replicas exchange Merkle Trees to identify parts of their replicated key ranges which are out of sync. A Merkle tree is a hierarchical hash verification: if the hash over the entire keyspace is not the same between two replicas, they will exchange hashes of smaller and smaller portions of the replicated keyspace until the out-of-sync keys are identified. This approach reduces unnecessary data transfer between replicas which contain mostly similar data.

Gossip

最后,随着分布式系统的增长,很难跟踪系统中每个节点的运行情况。这三个基于 Dynamo 的系统采用了一种古老的高中技术,称为Gossip 来跟踪其他节点。周期性地(每秒左右),一个节点将选择一个它曾经与之通信的随机节点,以交换系统中其他节点健康状况的知识。在提供这种交换时,节点了解哪些其他节点已关闭,并知道将客户端路由到何处以搜索密钥。


Gossip

Finally, as distributed systems grow, it is hard to keep track of how each node in the system is doing. The three Dynamo-based systems employ an age-old high school technique known as gossip to keep track of other nodes. Periodically (every second or so), a node will pick a random node it once communicated with to exchange knowledge of the health of the other nodes in the system. In providing this exchange, nodes learn which other nodes are down, and know where to route clients in search of a key.


结语 A Final Word

NoSQL生态系统仍处于起步阶段,我们讨论的许多系统的架构、设计和接口都可能会改变。本文的重要内容不是每个 NoSQL 系统当前所做的事情,而是导致构成这些系统的功能组合的设计决策。NoSQL 将大量设计工作留给了应用程序设计人员。了解这些系统的架构组件不仅可以帮助您构建下一个伟大的 NoSQL 合并,还可以让您负责任地使用当前版本。

The NoSQL ecosystem is still in its infancy, and many of the systems we've discussed will change architectures, designs, and interfaces. The important takeaways in this chapter are not what each NoSQL system currently does, but rather the design decisions that led to a combination of features that make up these systems. NoSQL leaves a lot of design work in the hands of the application designer. Understanding the architectural components of these systems will not only help you build the next great NoSQL amalgamation, but also allow you to use current versions responsibly.




Bibliography【参考书目】
[AF94] Rick Adams and Donnalyn Frey: !%@:: A Directory of Electronic Mail Addressing & Networks. O'Reilly Media, Sebastopol, CA, fourth edition, 1994.
[Ald02] Gaudenz Alder: The JGraph Swing Component. PhD thesis, ETH Zurich, 2002.
[BCC+05] Louis Bavoil, Steve Callahan, Patricia Crossno, Juliana Freire, Carlos E. Scheidegger, Cláudio T. Silva, and Huy T. Vo: "VisTrails: Enabling Interactive Multiple-View Visualizations". Proc. IEEE Visualization, pages 135–142, 2005.
[Bro10] Frederick P. Brooks, Jr.: The Design of Design: Essays from a Computer Scientist. Pearson Education, 2010.
[CDG+06] Fay Chang, Jeffrey Dean, Sanjary Ghemawat, Wilson C. Hsieh, Deborah A. Wallach, Mike Burrows, Tushar Chandra, Andrew Fikes, and Robert E. Gruber: "BigTable: a Distributed Storage System for Structured Data". Proc. 7th USENIX Symposium on Operating Systems Design and Implementation (OSDI'06). USENIX Association, 2006.
[CIRT00] P. H. Carns, W. B. Ligon III, R. B. Ross, and R. Thakur: "PVFS: A Parallel File System for Linux Clusters". Proc. 4th Annual Linux Showcase and Conference, pages 317–327, 2000.
[Com79] Douglas Comer: "Ubiquitous B-Tree". ACM Computing Surveys, 11:121–137, June 1979.
[CRS+08] Brian F. Cooper, Raghu Ramakrishnan, Utkarsh Srivastava, Adam Silberstein, Philip Bohannon, Hans Arno Jacobsen, Nick Puz, Daniel Weaver, and Ramana Yerneni: "PNUTS: Yahoo!'s Hosted Data Serving Platform". PVLDB, 1(2):1277–1288, 2008.
[DG04] Jeffrey Dean and Sanjay Ghemawat: "MapReduce: Simplified Data Processing on Large Clusters". Proc. Sixth Symposium on Operating System Design and Implementation, 2004.
[DHJ+07] Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, Gunavardhan Kakulapati, Avinash Lakshman, Alex Pilchin, Swaminathan Sivasubramanian, Peter Vosshall, and Werner Vogels: "Dynamo: Amazon's Highly Available Key-Value Store". SOSP'07: Proc. Twenty-First ACM SIGOPS Symposium on Operating Systems Principles, pages 205–220, 2007.
[FKSS08] Juliana Freire, David Koop, Emanuele Santos, and Cláudio T. Silva: "Provenance for Computational Tasks: A Survey". Computing in Science and Engineering, 10(3):11–21, 2008.
[FSC+06] Juliana Freire, Cláudio T. Silva, Steve Callahan, Emanuele Santos, Carlos E. Scheidegger, and Huy T. Vo: "Managing Rapidly-Evolving Scientific Workflows". International Provenance and Annotation Workshop (IPAW), LNCS 4145, pages 10–18. Springer Verlag, 2006.
[GGL03] Sanjay Ghemawat, Howard Gobioff, and Shun-Tak Leung: "The Google File System". Proc. ACM Symposium on Operating Systems Principles, pages 29–43, 2003.
[GL02] Seth Gilbert and Nancy Lynch: "Brewer's Conjecture and the Feasibility of Consistent Available Partition-Tolerant Web Services". ACM SIGACT News, page 2002, 2002.
[GR09] Adam Goucher and Tim Riley (editors): Beautiful Testing. O'Reilly, 2009.
[GLPT76] Jim Gray, Raymond Lorie, Gianfranco Putzolu, and Irving Traiger: "Granularity of Locks and Degrees of Consistency in a Shared Data Base". Proc. 1st International Conference on Very Large Data Bases, pages 365–394, 1976.
[Gra81] Jim Gray: "The Transaction Concept: Virtues and Limitations". Proc. Seventh International Conference on Very Large Data Bases, pages 144–154, 1981.
[Hor05] Cay Horstmann: Object-Oriented Design and Patterns. Wiley, 2 edition, 2005.
[HR83] Theo Haerder and Andreas Reuter: "Principles of Transaction-Oriented Database Recovery". ACM Computing Surveys, 15, December 1983.
[Kit10] Kitware: VTK User's Guide. Kitware, Inc., 11th edition, 2010.
[Knu74] Donald E. Knuth: "Structured Programming with Go To Statements". ACM Computing Surveys, 6(4), 1974.
[LA04] Chris Lattner and Vikram Adve: "LLVM: A Compilation Framework for Lifelong Program Analysis & Transformation". Proc. 2004 International Symposium on Code Generation and Optimization (CGO'04), Mar 2004.
[LCWB+11] H. Andrées Lagar-Cavilla, Joseph A. Whitney, Roy Bryant, Philip Patchin, Michael Brudno, Eyal de Lara, Stephen M. Rumble, M. Satyanarayanan, and Adin Scannell: "SnowFlock: Virtual Machine Cloning as a First-Class Cloud Primitive". ACM Transactions on Computer Systems, 19(1), 2011.
[Mac06] Matt Mackall: "Towards a Better SCM: Revlog and Mercurial". 2006 Ottawa Linux Symposium, 2006.
[MQ09] Marshall Kirk McKusick and Sean Quinlan: "GFS: Evolution on Fast-Forward". ACM Queue, 7(7), 2009.
[PGL+05] Anna Persson, Henrik Gustavsson, Brian Lings, Björn Lundell, Anders Mattson, and Ulf Ärlig: "OSS Tools in a Heterogeneous Environment for Embedded Systems Modelling: an Analysis of Adoptions of XMI". SIGSOFT Software Engineering Notes, 30(4), 2005.
[PPT+93] Rob Pike, Dave Presotto, Ken Thompson, Howard Trickey, and Phil Winterbottom: "The Use of Name Spaces in Plan 9". Operating Systems Review, 27(2):72–76, 1993.
[Rad94] Sanjay Radia: "Naming Policies in the Spring System". Proc. 1st IEEE Workshop on Services in Distributed and Networked Environments, pages 164–171, 1994.
[RP93] Sanjay Radia and Jan Pachl: "The Per-Process View of Naming and Remote Execution". IEEE Parallel and Distributed Technology, 1(3):71–80, 1993.
[Shu05] Rose Shumba: "Usability of Rational Rose and Visio in a Software Engineering Course". SIGCSE Bulletin, 37(2), 2005.
[Shv10] Konstantin V. Shvachko: "HDFS Scalability: The Limits to Growth". ;login:, 35(2), 2010.
[SML06] Will Schroeder, Ken Martin, and Bill Lorensen: The Visualization Toolkit: An Object-Oriented Approach to 3D Graphics. Kitware, Inc., 4 edition, 2006.
[SO92] Margo Seltzer and Michael Olson: "LIBTP: Portable, Modular Transactions for Unix". Proc 1992 Winter USENIX Conference, pages 9–26, January 1992.
[Spi03] Diomidis Spinellis: "On the Declarative Specification of Models". IEEE Software, 20(2), 2003.
[SVK+07] Carlos E. Scheidegger, Huy T. Vo, David Koop, Juliana Freire, and Cláudio T. Silva: "Querying and Creating Visualizations by Analogy". IEEE Transactions on Visualization and Computer Graphics, 13(6):1560–1567, 2007.
[SY91] Margo Seltzer and Ozan Yigit: "A New Hashing Package for Unix". Proc. 1991 Winter USENIX Conference, pages 173–184, January 1991.
[Tan06] Audrey Tang: "–O fun: Optimizing for Fun". http://www.slideshare.net/autang/ofun-optimizing-for-fun, 2006.
[Top00] Kim Topley: Core Swing: Advanced Programming. Prentice-Hall, 2000.
来自:http://aosabook.org/en/nosql.html



Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/150142