孜孜不倦的程序员
Cassandra NoSQL 数据库,第 2 部分:编程
在我 8 月 2012年列中,"卡桑德拉 NoSQL 数据库:我快速入门,"审查 Apache 卡桑德拉。它被称为"开放源代码,分发,权力下放,弹性可扩展、 高可用、 容错、 tuneably 一致,亚马逊发电机和谷歌马力其数据模型为基础及其分布设计的面向列的数据库"在书中,"卡桑德拉:权威指南"(O'Reilly 媒体,2010年)。更精确,我看着如何安装卡桑德拉 (其中,因为它是一个基于 Java 的数据库,也需要 Java 虚拟机启动并运行您的计算机上,如果你没有一个已经)、 如何连接到它从命令行和看上去像什么及其数据模型。重复因为它是明显不同的结构比大多数开发人员所熟悉的关系数据库的数据模型熊。
讨论时最后一次 (msdn.microsoft.com/magazine/JJ553519)、 卡桑德拉是"面向列的"数据存储,这意味着而不是存储具有相同的结构按照固定结构 (表架构) 排列的数据的元组、 卡桑德拉存储"列家庭"的"keyspaces"。在更具描述性的术语中,卡桑德拉将密钥值与不同数量的可能是从一个"行"到另一个完全不同的名称/值对 (列) 相关联。
例如,考虑密钥空间创建最后一次的"地球",与列家族命名为"人,"我将写成的行 (可能或不可能) 看起来像这样:
RowKey: tedneward
ColumnName:"FirstName", ColumnValue:"Ted"
ColumnName:"LastName", ColumnValue:"Neward"
ColumnName:"Age", ColumnValue:41
ColumnName:"Title", ColumnValue:"Architect"
RowKey: rickgaribay
ColumnName:"FirstName", ColumnValue:"Rick"
ColumnName:"LastName", ColumnValue:"Garibay"
RowKey: theartistformerlyknownasprince
ColumnName:"Identifier", ColumnValue: <image>
ColumnName:"Title", ColumnValue:"Rock Star"
正如您所看到的每个"行"包含在概念上类似的数据,但不是所有的"行"将具有相同的数据,根据开发人员或业务需要来存储任何特定行键。 我不知道里克的年龄,所以我不能将它存储。 在关系数据库中,如果架构授权年龄是一个不可为空的列,我不能有存储瑞克在所有。 卡桑德拉说,"为什么不呢?"
我以前的列显示插入和删除数据从命令行中,但这并不是特别有帮助,如果目标是编写的应用程序将数据访问和存储数据。 所以,没有进一步的背景,让我们深入它所需的应用程序读取和存储写入卡桑德拉。
卡桑德拉、 澳卡桑德拉,为什么你卡桑德拉吗?
若要开始,需要从 Microsoft.NET 框架连接到卡桑德拉。 这样做涉及两种方法之一:我可以使用本机的 Apache 节俭 API,或者我可以使用第三方在本机的节俭 API 的包装。 节俭是二进制的远程过程调用的工具包 (打赌你还没想到几年) 的 DCOM 或 CORBA 或.NET 远程处理的很多方面类似。 这是与卡桑德拉通信特别是低级方法和节俭了 C# 支持,而不是微不足道的所有的起床并且正在运行。 节俭的替代品包括 FluentCassandra、 卡桑德拉-夏普,Cassandraemon 和 Aquiles (跟腱,使古希腊主题活着和井的西班牙语翻译)。 所有这些都是开源和卡桑德拉 API 提供一些好的抽象。 为此列,我要使用 FluentCassandra,但其中任何似乎很好,工作尽管奇数互联网火焰战争。
FluentCassandra 是可用作为 NuGet 包,所以最简单的方法来开始火起来 NuGet 软件包管理器以在 Visual Studio 测试项目 (所以我可以写勘探测试) 和做"安装软件包 FluentCassandra"。(写这篇文章的最新版本是 1.1.0 驱动程序)。一旦这样做,并且我已经核实卡桑德拉服务器仍在运行后我玩弄它 8 月列,我可以写第一勘探测试:连接到服务器。
FluentCassandra 生活在"FluentCassandra"的命名空间和两个嵌套的命名空间 ("连接"和"类型"),这样将那些中,再然后写一个测试以查看有关连接到数据库:
private static readonly Server Server =
new Server("localhost");
TestMethod]
public void CanIConnectToCassandra()
{
using (var db = new CassandraContext(keyspace: "system",
server:Server))
{
var version = db.DescribeVersion();
Assert.IsNotNull(version);
testContextInstance.WriteLine("Version = {0}", version);
Assert.AreEqual("19.30.0", version);
}
}
注意你读这个的时候,它是可能的版本号将不同于我所写的时候,所以如果这第二个断言失败时,检查输出窗口以查看返回的字符串。 (记住,勘探测试是有关测试您了解的 API,所以并不写入输出是一个糟糕的主意是自动化的单元测试中。
CassandraContext 类有五个不同的重载,用于连接到正在运行的卡桑德拉服务器,它们都很容易推断出 — — 他们都处理一种或另一种连接信息。 在此特定情况下,因为我还没创建密钥的空间,我想向存储区 (和以后读取) 数据,我是连接到"系统"密钥空间,卡桑德拉用于存储系统性的各种细节,很多关系数据库最有预留数据库元数据和安全的一个实例以同样的方式和这种。 但这意味着我不想将写入到该系统密钥空间 ; 我要创建我自己,这形成了下一步的勘探测试,如中所示图 1。
图 1 创建系统密钥空间
[TestMethod]
public void DoesMyKeyspaceExistAndCreateItIfItDoesnt()
{
using (var db = new CassandraContext(keyspace: "system",
server:Server))
{
bool foundEarth = false;
foreach (CassandraKeyspace keyspace in db.DescribeKeyspaces())
{
Apache.Cassandra.KsDef def = keyspace.GetDescription();
if (def.Name == "Earth")
foundEarth = true;
}
if (!foundEarth)
{
var keyspace = new CassandraKeyspace(new
CassandraKeyspaceSchema
{
Name = "Earth"
}, db);
keyspace.TryCreateSelf();
}
Assert.IsTrue(db.KeyspaceExists("Earth"));
}
}
诚然,通过在数据库中的所有 keyspaces 循环是不必要的 — — 我不要在这里,证明,并通过,底层的节俭基于 API 扫视和"Apache.Cassandra.KsDef"在其中键入 FluentCassandra API 中的地方是其中之一。
我现在有了密钥空间,我需要至少一个列家庭内该密钥空间。 最简单的方法来创建此使用卡桑德拉的查询语言 (CQL),隐约类似 SQL 语言,如中所示图 2。
图 2 创建一个列家庭使用卡桑德拉的查询语言
[TestMethod]
public void CreateAColumnFamily()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
CassandraColumnFamily cf = db.GetColumnFamily("People");
if (cf == null)
{
db.ExecuteNonQuery(@"CREATE COLUMNFAMILY People (
KEY ascii PRIMARY KEY,
FirstName text,
LastName text,
Age int,
Title text
);");
}
cf = db.GetColumnFamily("People");
Assert.IsNotNull(cf);
}
}
CQL 的危险是其故意类似于 SQL 语法结合容易误解"卡桑德拉了列,因此它必须具有像关系数据库表"把戏粗心的开发人员到思维的关系的条款。 这将导致概念的假设,是大错特错。 例如,请考虑中的列图 2。 在关系数据库中,将此列家族中允许那些五列。 卡桑德拉,这些是公正的"指导原则"(在莫名"加勒比海盗"排序的方式)。 但是,替代 (以根本不使用 CQL) 到目前为止是吸引力:卡桑德拉提供了 API TryCreateColumnFamily (未显示),但无论多少次我试着换我围绕它的头,这还是觉得更笨拙和 CQL 方法相比,令人困惑。
“数据,数据,还是数据! 无木不成林!”
列家庭后,FluentCassandra API 的实际权力出现某些对象存储到数据库中,如中所示图 3。
存储在数据库中的对象的图 3
[TestMethod]
public void StoreSomeData()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var peopleCF = db.GetColumnFamily("People");
Assert.IsNotNull(peopleCF);
Assert.IsNull(db.LastError);
dynamic tedneward = peopleCF.CreateRecord("TedNeward");
tedneward.FirstName = "Ted";
tedneward.LastName = "Neward";
tedneward.Age = 41;
tedneward.Title = "Architect";
db.Attach(tedneward);
db.SaveChanges();
Assert.IsNull(db.LastError);
}
}
请注意使用 C# 4.0 的"动态"设施,以强化思想列家庭不是严格类型的名称/值对的集合。 这允许的 C# 代码以反映面向列的数据存储区的性质。 当我存储密钥空间成几个更多的人,如中所示,我可以看到这图 4。
图 4 密钥空间中存储更多的人
[TestMethod]
public void StoreSomeData()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var peopleCF = db.GetColumnFamily("People");
Assert.IsNotNull(peopleCF);
Assert.IsNull(db.LastError);
dynamic tedneward = peopleCF.CreateRecord("TedNeward");
tedneward.FirstName = "Ted";
tedneward.LastName = "Neward";
tedneward.Age = 41;
tedneward.Title = "Architect";
dynamic rickgaribay = peopleCF.CreateRecord("RickGaribay");
rickgaribay.FirstName = "Rick";
rickgaribay.LastName = "Garibay";
rickgaribay.HomeTown = "Phoenix";
dynamic theArtistFormerlyKnownAsPrince =
peopleCF.CreateRecord("TAFKAP");
theArtistFormerlyKnownAsPrince.Title = "Rock Star";
db.Attach(tedneward);
db.Attach(rickgaribay);
db.Attach(theArtistFormerlyKnownAsPrince);
db.SaveChanges();
Assert.IsNull(db.LastError);
}
}
再次,只是要开车回家点,注意如何 Rick 家乡列,而不较早前此列家庭的描述中指定。 这是完全可以接受的而且很常见。
此外请注意 FluentCassandra API 提供了"LastError"属性,它包含对数据库引发的最后一个异常的引用。 这可用于检查数据库的状态已经不当已知 (如一套可能已经吃了,引发的异常的调用返回时,或者如果数据库被配置为不引发异常)。
再一次的感觉
连接到数据库、 创建密钥空间 (和稍后删除它),定义列家庭和放一些种子数据 — — 我大概要想要做这些事情很多内这些测试。 该序列是代码的伟大的候选对象放入预先测试安装和后期测试拆卸方法。 通过删除后的密钥空间并重新创建它在每个测试之前,我保留数据库精粹和已知的状态在每次我运行测试,如中所示图 5。 真好玩。
运行测试的图 5
[TestInitialize]
public void Setup()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var keyspace = new CassandraKeyspace(new CassandraKeyspaceSchema {
Name = "Earth",
}, db);
keyspace.TryCreateSelf();
db.ExecuteNonQuery(@"CREATE COLUMNFAMILY People (
KEY ascii PRIMARY KEY,
FirstName text,
LastName text,
Age int,
Title text);");
var peopleCF = db.GetColumnFamily("People");
dynamic tedneward = peopleCF.CreateRecord("TedNeward");
tedneward.FirstName = "Ted";
tedneward.LastName = "Neward";
tedneward.Age = 41;
tedneward.Title = "Architect";
dynamic rickgaribay = peopleCF.CreateRecord("RickGaribay");
rickgaribay.FirstName = "Rick";
rickgaribay.LastName = "Garibay";
rickgaribay.HomeTown = "Phoenix";
dynamic theArtistFormerlyKnownAsPrince =
peopleCF.CreateRecord("TAFKAP");
theArtistFormerlyKnownAsPrince.Title = "Rock Star";
db.Attach(tedneward);
db.Attach(rickgaribay);
db.Attach(theArtistFormerlyKnownAsPrince);
db.SaveChanges();
}
}
[TestCleanup]
public void TearDown()
{
var db = new CassandraContext(keyspace: "Earth", server: Server);
if (db.KeyspaceExists("Earth"))
db.DropKeyspace("Earth");
}
看看我的作品,你们强大,和绝望!
卡桑德拉从读取数据需要几的形式。 第一个是以获取数据列族在中所示的 CassandraColumnFamily 对象上使用 Get 方法图 6。
图 6 使用 Get 方法将数据获取
[TestMethod]
public void StoreAndFetchSomeData()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var peopleCF = db.GetColumnFamily("People");
Assert.IsNotNull(peopleCF);
Assert.IsNull(db.LastError);
dynamic jessicakerr = peopleCF.CreateRecord("JessicaKerr");
jessicakerr.FirstName = "Jessica";
jessicakerr.LastName = "Kerr";
jessicakerr.Gender = "F";
db.Attach(jessicakerr);
db.SaveChanges();
Assert.IsNull(db.LastError);
dynamic result = peopleCF.Get("JessicaKerr").FirstOrDefault();
Assert.AreEqual(jessicakerr.FirstName, result.FirstName);
Assert.AreEqual(jessicakerr.LastName, result.LastName);
Assert.AreEqual(jessicakerr.Gender, result.Gender);
}
}
这是很大,如果我知道的密钥之前的时间,但很多时候,情况并非如此。 事实上,这是值得商榷,大部分时间,准确记录不会知道。 所以,(未显示) 的另一种方法是使用 FluentCassandra LINQ 集成来编写样式 LINQ 查询。 但是这不是不如传统 LINQ,相当灵活。 列名称不事先知道的因为它是很难在数据库中,例如写 LINQ 查询以查找 (看列家族中的姓氏名称/值对) 的所有 Newards。
幸运的是,CQL 骑去救援,如中所示图 7。
图 7 使用卡桑德拉 LINQ 集成编写样式 LINQ 查询
[TestMethod]
public void StoreAndFetchSomeDataADifferentWay()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var peopleCF = db.GetColumnFamily("People");
Assert.IsNotNull(peopleCF);
Assert.IsNull(db.LastError);
dynamic charlotte = peopleCF.CreateRecord("CharlotteNeward");
charlotte.FirstName = "Charlotte";
charlotte.LastName = "Neward";
charlotte.Gender = "F";
charlotte.Title = "Domestic Engineer";
charlotte.RealTitle = "Superwife";
db.Attach(charlotte);
db.SaveChanges();
Assert.IsNull(db.LastError);
var newards =
db.ExecuteQuery("SELECT * FROM People WHERE LastName='Neward'");
Assert.IsTrue(newards.Count() > 0);
foreach (dynamic neward in newards)
{
Assert.AreEqual(neward.LastName, "Neward");
}
}
}
但是请注意,是否运行此代码,它将失败 — — 卡桑德拉不会让我作为筛选条件使用列家庭内的名称/值对,除非它显式定义索引。 这样做需要另一个 CQL 语句:
db.ExecuteNonQuery(@"CREATE INDEX ON People (LastName)");
通常,我想要设置了当时列家庭创建的。 也注意到卡桑德拉是架构不重要,因为"选择 *"该查询的一部分是有点欺骗性 — — 它将返回所有的名称/值对在列家庭中,但这并不意味着每个记录将具有每个列。 这意味着,然后,带有查询"WHERE 性别 = 'F'"不包含"性别"列中,不考虑留下 Rick、 Ted 和艺术家以前已知作为王子 》 的记录不会再考虑。 这是完全不同的一个关系数据库管理系统 (尽管我常常鸭这一责任通过存储"空"在这些列中审议的一些是大忌),每个表中的行必须具有为每个列的值。
完整的 CQL 语言是太多,在这里,描述,但完全引用卡桑德拉 Web 站点可用 bit.ly/MHcWr6。
现在,环绕
我不很受不了诅咒女只是尚未 — — 虽然出获取数据卡桑德拉的是给开发人员最有趣的部分 (因为这是他们所做的所有天)、 多节点配置也是相当大的一部分的卡桑德拉的故事。 这样做对单个 Windows 框 (用于发展目的 ; 您将看到如何将更容易实现跨多个服务器) 是不是微不足道的这就是为什么我会做下次给卡桑德拉的探讨。
现在,您编码愉快 !
Ted Neward 是 Neudesic LLC. 的体系结构顾问。他曾写过 100 多篇文章,独自撰写或与人合著过十几本书,包括《Professional F# 2.0》(Wrox,2010 年)。他是 MVP F # 和 Java 的著名的专家,并在世界各地的 Java 和.NET 会议上演讲。他征求意见,并定期指导 — — 与他联系。 ted@tedneward.com 或 Ted.Neward@neudesic.com 如果你感兴趣让他来与您的团队一起工作。在他博客 blogs.tedneward.com 和后面可以在 Twitter 上 Twitter.com/tedneward。
衷心感谢以下技术专家对本文的审阅:凯利莫斯