文档章节

【译】Cassandra数据模型

Landas
 Landas
发布于 2016/12/21 18:53
字数 2735
阅读 50
收藏 0

        本文是英文贴的翻译,可以直接查看英文原文
        选择正确的数据模型正是使用Cassandra最困难的一部分。如果诸位有相关开发经验,就会发现CQL虽然看起来很熟悉,但是使用起来却完全不同。
        本文将说明设计Cassandra schema的基本准则。在项目中遵守它们不仅有马上可见的益处,而且可以保证在今后扩展节点时Cassandra的性能保持线性增长。

无效的准则

        有关系型数据库经验的开发者经常会将之前的经验带入Cassandra。为了避免在这些并不重要的事(对Cassandra而言)上耗费时间,先指出一些无效准则:

减少写库数量

        虽然在Cassandra中写数据并不完全免费,但是非常非常廉价。Cassandra天生就是为了高速率的写而优化设计的。在Cassandra中几乎总是应该使用冗余的写来提高读取效率。请记住,读比写昂贵很多并且更难以调优。

减少数据复制

        别害怕,Cassandra所做的事情就是复制数据。磁盘空间通常是最廉价的资源(比起CPU、memory、磁盘IO、网络等等),整个Cassandra正是架构在这个事实之上。你常常需要复制数据来提升读的效率。
另外Cassandra没有JOINs语句(当然,在分布式设计中你不会真的想使用它!)

基本准则

        你的数据模型应遵循如下两个高层级目标:
1. 让数据在集群(cluster)中尽可能的均匀分布
2. 从尽可能少的分区(partition)中查找数据
        上面的两条是最最重要的准则。你应该首选要学会在项目中如何做好这两点。

均匀分布数据

        一般来说,希望集群中的各个节点数据量尽可能相等。在Cassandra中,数据通过分区键(partition key)来分区存储。分区键即主键(PRIMARY KEY)的第一个元素。
        分区键决定了数据存放在哪个node上。所以数据能够均匀分布的关键就在于:选择一个好的主键

最小化分区查找

        分区包含的是有着相同分区键的数据行。当你发起一个查询时,请从尽可能少的分区中读取数据。
这条准则的重要性在于各个分区可能位于不同的节点。协调者(coordinator)将向各个节点发起不同命令。这将带来额外的负荷与延迟。
        即使所要查询的分区都位于一个节点,跨分区的查询也要昂贵许多。

矛盾?

        既然应该从尽可能少的分区中查询数据,那为何不将所有数据都存到一个分区中?因为这违反了第一条准则。
        可以看到这两条准则会相互矛盾,所以实际项目中常常需要权衡。

从查询来构造模型

        实现最小化分区查找的方法就是使数据模型完全切合你的查询。不要从关系、对象等等来倒推模型,从查询入手!
第一步. 确定真正需要支持什么查询
        试着决定你真正需要支持的查询。尽量考虑那些甚至一开始没想到的需求。例如:
        * 按照属性分组
        * 按照属性排序
        * 根据条件过滤
        * 在结果集中去重
        为了追求效率,查询条件的变化经常导致数据模型的变化。
第二步. 尽可能的让你的查询只读取一个分区
        实践中这常常意味着你需要为每一个查询单独建立一张表。换种说法,每张表意味着为你不同的查询事先准备好了答案。如果需要不同的答案,那就建立不同的表。这就是对读的优化。
        永远记住,数据复制是常规操作。你的许多表可能包含重复的数据。

示例

        用一些小例子来看看上面这些准则的最佳实践。

示例一 用户查询

        最顶层的需求是“我们有一些用户,并且需要查询他们”。设计步骤如下:
        1. 细化查询
比如需要按用户名或者email地址查询。每一种查询方式都需要得到用户的所有详细信息。
        2. 按每次只查一个分区的目标来建表
        既然两种查询都需要得到用户的全信息,那就建两张表:

CREATE TABLE users_by_username (
    username text PRIMARY KEY,
    email text,
    age int
)
 
CREATE TABLE users_by_email (
    email text PRIMARY KEY,
    username text,
    age int
)

        回顾一下上面提到的两条准则:
        数据均匀分布?数据将按照不同的用户进行分区,满足。
        最小化分区读?每个查询只读一个分区,满足。
        现在假设我们需要按照上面提到的无效目标进行优化,数据模型将会变成这样:

CREATE TABLE users (
    id uuid PRIMARY KEY,
    username text,
    email text,
    age int
)
 
CREATE TABLE users_by_username (
    username text PRIMARY KEY,
    id uuid
)
 
CREATE TABLE users_by_email (
    email text PRIMARY KEY,
    id uuid
)

        这种数据模型确实是均匀分布的,但是它的缺点是需要查询两个分区。:(

示例二 用户组

        现在来改变一下示例一中的顶层需求:用户是按组进行划分的,我们需要按照组来查找用户
        1. 细化查询条件
        我们需要查找特定组中的所有用户信息,不关心排序。
        2. 按每次只查一个分区的目标来建表
        如何将一个组放到一个分区中 ? 这需要用到复合主键:

CREATE TABLE groups (
    groupname text,
    username text,
    email text,
    age int,
    PRIMARY KEY (groupname, username)
)

        这里的主键包含组名和用户名:组名作为分区键,用户名作为聚合键(clustering key)。这样一个组就完全属于同一个分区。
        在特定组中,数据以用户名为序。查询语句很简单:

SELECT * FROM groups WHERE groupname = ?

         很容易看出这种设计满足第二条准则。但是并不是那么满足第一条。如果我们有着大量的小组数量,每个里面有着少量百用户,这时数据才会均匀分布。
        但是如果仅仅有着一个组,里面包含着大量用户,那么这个组所在的节点(或该节点的复制组)将会承担所有负载。

        如果需要将数据更加均匀的分布,我们需要使用一些策略。首先需要在主键中增加一列,使用联合分区键:

CREATE TABLE groups (
    groupname text,
    username text,
    email text,
    age int,
    hash_prefix int,
    PRIMARY KEY ((groupname, hash_prefix), username)
)

        新增加的hash_prefix是用户名的hash的前缀。比如,可以是hash对4取模的第一个字节。hash_prefix和groupname组成了联合分区键。这导致了一个组的所有用户会分布在四个分区中。
        我们的数据变得更均匀了,但是现在一个查询将要跨越四个分区。这就上文所提到矛盾的例子。你需要从你的具体使用环境中找到平衡。
        如果查询十分频繁,而且你的每个分组不是特别大,将对4取模改为对2取模可能是一个好的选择。
        反过来说,如果查询不频繁,并且要查询的分组会特别大,将4改为10将会更合适。

        在这个例子中,我们在每个分区中复制了相同用户的所有信息。你可能会尝试着这样做来减少数据复制:

CREATE TABLE users (
    id uuid PRIMARY KEY,
    username text,
    email text,
    age int
)
 
CREATE TABLE groups (
    groupname text,
    user_id uuid,
    PRIMARY KEY (groupname, user_id)
)

        这种做法中,如果说一个分组有着1000个用户,那么查询时我们就要读取1001个分区!如果读取频繁,那么这种做法是极端不可取的。
        另一方面,如果按组读取的频率极低,但是更新用户信息(用户名)特别频繁,此时这种做法倒还有可取之处。
        记住设计数据模型时将读/写频率考虑进去!

示例三 用户组和入组时间

        现在在上个例子中加入入组时间,一次读取x个新入组用户。新表如下:

CREATE TABLE group_join_dates (
    groupname text,
    joined timeuuid,
    username text,
    email text,
    age int,
    PRIMARY KEY (groupname, joined)
)

        这里使用了timeuuid(类似于timestamp,但是两条记录不会相同)作为聚合列。在一个组(分区)中,数据按照用户入组时间排列。查询语句如下:

SELECT * FROM group_join_dates
    WHERE groupname = ?
    ORDER BY joined DESC
    LIMIT ?

        这样做可以有效的保证查询效率。但是总是使用ORDER BY并不是最高效的。更高效的做法表结构如下:

CREATE TABLE group_join_dates (
    groupname text,
    joined timeuuid,
    username text,
    email text,
    age int,
    PRIMARY KEY (groupname, joined)
) WITH CLUSTERING ORDER BY (joined DESC)

        下面是能为查询效率带来那么一点轻微提升的查询:

SELECT * FROM group_join_dates
    WHERE groupname = ?
    LIMIT ?

        前一个例子中,因为在组变得过大时数据将不会均匀分布,所以我们用某种随机方式来进行分区(用户名hash)。在这个例子中,我们可以利用时间段来进行分区。比如:

CREATE TABLE group_join_dates (
    groupname text,
    joined timeuuid,
    join_date text,
    username text,
    email text,
    age int,
    PRIMARY KEY ((groupname, join_date), joined)
) WITH CLUSTERING ORDER BY (joined DESC)

        这次利用了入组时间作为复合分区键,每一天将开始一个新的分区。当查询x个新用户的时候,我们首先查询当天的分区,然后是昨天,直到结果集包含x个用户。这样在得到最终结果前可能要查询多个分区。
        为了最小化查询分区数量,应该尝试选择一个合适的时间范围,使得你的查询只会查到一个或两个分区。比如说业务上经常要查的是10个最新的用户,同时每个组每天会增加三个人左右。
        合适的做法是将4天作为一个时间段,而不是一天。可以截取时间戳到合适的长度。如:

now = time()
four_days = 4 * 24 * 60 * 60
shard_id = now - (now % four_days)

总结

        本文的基本原则适用于目前所有的Cassandra版本,很可能也同样适用于未来的版本。其他的一些数据模型面临的问题(如tombstone),当然也需要考虑,但不同的是它们不太可能出现在Cassandra的未来版本中。

        还有Cassandra的其他一些特性(如 集合、用户自定义类型、 静态列)也有助于减少查询分区数目。设计时请别忘了它们。
        想更深入的学习,请看这里。祝各位好运!!

© 著作权归作者所有

Landas
粉丝 6
博文 40
码字总数 90399
作品 0
深圳
程序员
私信 提问
为什么在大数据处理中Cassandra与Spark如此受欢迎?

【51CTO.com快译】随着现代云应用对正常运行时间及性能水平的要求逐步提高,已经有越来越多用户开始将注意力集中在Apache Cassandra数据库身上。 那么,为什么要选择Apache Cassandra?这套分...

明惠
05/07
0
0
What is Apache Hadoop?

本文来自:自译+百度 Apache Hadoop 是一款可靠、可升级、分布式计算的开源软件。 Apache Hadoop 的开源软件库是专门为处理跨大数据簇而设计的处理模型族。它主要被设计用来按比例从一个到成...

卿卿的博文
2017/12/18
0
0
分布式 Key-Value 存储系统:Cassandra 入门

Cassandra 的数据存储结构 Cassandra 的数据模型是基于列族(Column Family)的四维或五维模型。它借鉴了 Amazon 的 Dynamo 和 Google's BigTable 的数据结构和功能特点,采用 Memtable 和 ...

黄平俊
2010/07/16
2.8K
0
【译】Spark Streaming 框架在 5G 中的应用

原文链接: Applying the Spark Streaming framework to 5G 我们已经很长时间没有更新流处理框架的相关博客(apache-storm-vs-spark-streaming 和 apache-storm-performance-tuners),这次想...

开源大数据EMR
06/20
0
0
考虑 Apache Cassandra 数据库

简介 在数据库历史文章 “What Goes Around Comes Around”(参阅 参考资料)中,Michal Stonebraker 详细描述了存储技术是如何随着时间的推移而发展的。实现关系模型之前,开发人员曾尝试过...

ihaolin
2014/08/17
127
0

没有更多内容

加载失败,请刷新页面

加载更多

Executor线程池原理与源码解读

线程池为线程生命周期的开销和资源不足问题提供了解决方 案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。 线程实现方式 Thread、Runnable、Callable //实现Runnable接口的...

小强的进阶之路
40分钟前
5
0
maven 环境隔离

解决问题 即 在 resource 文件夹下面 ,新增对应的资源配置文件夹,对应 开发,测试,生产的不同的配置内容 <resources> <resource> <directory>src/main/resources.${deplo......

之渊
今天
8
0
详解箭头函数和普通函数的区别以及箭头函数的注意事项、不适用场景

箭头函数是ES6的API,相信很多人都知道,因为其语法上相对于普通函数更简洁,深受大家的喜爱。就是这种我们日常开发中一直在使用的API,大部分同学却对它的了解程度还是不够深... 普通函数和...

OBKoro1
今天
7
0
轻量级 HTTP(s) 代理 TinyProxy

CentOS 下安装 TinyProxy yum install -y tinyproxy 启动、停止、重启 # 启动service tinyproxy start# 停止service tinyproxy stop# 重启service tinyproxy restart 相关配置 默认...

Anoyi
今天
2
0
Linux创建yum仓库

第一步、搞定自己的光盘 #创建文件夹 mkdir -p /media/cdrom #挂载光盘 mount /dev/cdrom /media/cdrom #编辑配置文件使其永久生效 vim /etc/fstab 第二步,编辑yun源 vim /ect yum.repos.d...

究极小怪兽zzz
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部