关系型数据库管理系统(RDBMS)是存储和使用数据最常用的系统,但对于超大量数据,这些数据库的扩展性不是很好。
近年来,由于对关系型数据库替代产品的需求日益增长, NoSQL 的概念已经受到广泛的欢迎。NoSQL 背后的最大动机是可扩展性。NoSQL 数据库解决方案提供了一种存储和使用超大量数据的方法,而且开销更小,工作量更少,性能更好,停机时间更短。
Apache Cassandra 是一个基于列的 NoSQL 数据库。它是 Facebook 为推动其收件箱搜索功能而开发的,后来成为 Apache 的开源项目。 Twitter 、 Digg 、Reddit 及其他许多组织都已经开始使用它。
Cassandra 本身提供了一个非常基础的交互式命令行接口(CLI)。开发人员可以使用 CLI 连接到集群中的远程节点,创建或更新模式以及设置和检索记录。
对于 Cassandra 管理员而言,CLI 是一个有用的工具。即使只提供了基础的命令,它也是一个很好的例子,从中可以知道如何实现 Cassandra 客户端。要开发自定义的 Cassandra 客户端甚至是扩展 CLI 工具,必须得了解 CLI 的内部工作原理。
本文将使用 JArchitect 工具和 CQLinq 语言分析 CLI 的代码库,以探究 CLI 的架构模型。JArchitect 工具用来分析代码结构,并指定设计原则,以获得更好的代码质量。借助 JArchitect,软件质量可以用代码度量进行测量,用“图(graph)”和“树图(treemap)”进行可视化,并用标准和自定义的规则来执行。
下面是分析得出的依赖关系图:
Cassandra 使用了若干大家熟知的 jar 包,如 antlr、log4j、slf4j、commons-lang,也使用了若干大家不怎么知道的 jar 包,比如下面这些:
- Libthrift :它是一个跨多种编程语言和用例的 API,其目标是尽可能高效和无缝地实现跨语言通信和数据序列化的可靠性和高性能。
- Snakeyaml :YAML 是一个为人工可读性和与脚本语言交互而设计的数据序列化格式。Cassandra 的配置文件用了这一格式。
- Jackson :一个高性能 JSON processor。
- Snappy :它是一个用 C++ 编写的快速压缩 / 解压缩程序,最初由 Google 开发,snappy-java 是其 Java 版本。
- High-scale-lib :它是一个并发和高扩展性实用程序的集合,其目的是为了直接取代包 java.util.* 或者 java.util.concurrent.* 中的集合类,而当许多 CPU 并发使用集合时性能更好。
下图的矩阵图是对这些 JAR 文件之间依赖权重的更为详细的说明。
Cassandra**** 命令行接口
包 org.apache.cassandra.cli 实现了命令行接口逻辑,入口是 CliMain 类。
使用下面的 CQLinq 查询查找 main 方法调用的方法:
from m in Methods where m.IsUsedBy ("org.apache.cassandra.cli.CliMain.main(String[])") select new { m, m.NbBCInstructions }
Main 方法用了 JLine ,这是一个处理控制台输入的 Java 库。通过它,开发人员不需要太费力就可以写出很棒的 CLI 应用程序。它对命令历史、Tab 键自动补全、行编辑、自定义键绑定以及字符屏蔽提供了开箱即用的支持。
以下是 main 方法用到的两个有趣的方法:
- connect:该方法用于连接 Cassandra 数据库服务器。
- processStatetementInteractive:该方法用于执行来自用户的命令。
CLI**** 与 Cassandra 服务器之间的通信
在与 Cassandra 服务器进行交互之前,客户端必须使用 connect 方法连接到服务器。
下面的查询查找 connect 方法直接或间接使用的所有方法:
from m in Methods let depth0 = m.DepthOfIsUsedBy("org.apache.cassandra.cli.CliMain.connect (String,int)") where depth0 >= 0 orderby depth0 select new { m, depth0 }
CLI 使用 Thrift 库与服务器进行通信,Thrift 允许开发人员用一个简单的定义文件定义数据类型和服务接口。将这个文件作为输入,编译器会生成代码,开发人员可以用这些代码轻松地构建 RPC 客户端和服务器,从而实现跨编程语言的无缝通信。无需编写大量的样板代码来序列化和传输对象以及调用远程代码,开发人员就可以马上开始处理业务。
下面是 Thrift 服务器实现的一个简单例子:
Thrift 服务器实现了 org.apache.thrift.server.TServer 接口,其构造函数接收一个 processor 和一个服务器传输规范类的实例作为参数。Processor 需要一个 handler 来处理传入的请求。
让我们在 Cassandra 服务器中找出所有的这些组成部分。为了做到这一点,我们可以从找出所有继承了 TServer 类的类开始。
public class Server { public static class SomethingHandler implements Something.Iface { public SomethingHandler() {} public int ping() { return 1; } } public static void main(String [] args) { SomethingHandler handler = new SomethingHandler(); Something.Processor processor = new Something.Processor(handler); TServerTransport serverTransport = new TServerSocket(9090); TServer server = new TSimpleServer(processor, serverTransport); // 下面的语句适用于多线程服务器 // server = new TThreadPoolServer(processor, serverTransport) server.serve(); } }
Thrift 服务器实现了 org.apache.thrift.server.TServer 接口,其构造函数接收一个 processor 和一个服务器传输规范类的实例作为参数。Processor 需要一个 handler 来处理传入的请求。
让我们在 Cassandra 服务器中找出所有的这些组成部分。为了做到这一点,我们可以从找出所有继承了 TServer 类的类开始。
from t in Types let depth0 = t.DepthOfDeriveFrom("org.apache.thrift.server.TServer") where depth0 >= 0 orderby depth0 select new { t, depth0 }
Cassandra 定义了如下类:
CustomTThreadPoolServer:它是 Apache Thrift 类 TThreadPoolServer 的一个稍作修改的版本。TThreadPoolServer 类会使用一个线程池为传入的请求提供服务。
CustomerTHsHaServer:该服务器的目的是避免 IO 粘在一个 CPU 上。为了获得更大的吞吐量,它将 IO 任务分配给多个线程。Selector 线程的数目可以与可用的 CPU 数目相同。
CustomTNonBlockingServer:它使用一个非阻塞的套接字传输方式。
以下是 ThriftServer 启动时所做的工作:
该过程会使用工厂类创建一个 TServer,并创建一个 CassandraServer handler 来处理传入的请求,后者实现了 Cassandra.Iface 接口,其中包含了 Cassandra 支持的所有命令。下图展示了该接口的部分方法:
正如前面的 Thrift 服务器示例所示,需要用 processor 处理传入的请求;所有这些 processor 都继承了 ProcessFunction 类。
下面是部分 Cassandra processor:
在找出 Cassandra Thrift 服务器的组成部分之后,让我们回到客户端,找出 main 方法调用 connect 方法时发生了什么。
org.apache.thrift.TServiceClient用于客户端和服务器之间的通信,它调用 sendBase 方法向 Thrift 服务器发送消息。
在服务器上,login processor 接收这一请求,并调用 login 方法。
下面的依赖关系图展示了 login 方法调用的部分方法。
下面会通过添加一个新方法 MyMethod 进入到扩展 CLI 的步骤。
在查明 CLI 的内部工作原理之后,我们可以很容易地给它添加新方法,下面是所需的主要步骤:
I- 扩展服务器:
- 向Cassandra.Iface接口中添加方法
- 向CassandraServer类中添加该方法的实现
- 新建一个继承ProcessFunction
的类Cassandra.Processor.MyMethod。 - 在Cassandra.Processor.getProcessMap方法返回的 Map 中添加上一步中新建的 processor 的一个实例。
II- 扩展客户端:
- 新增一个开关项,并在CliOptions.processArgs方法中对其进行处理。
- 向Cassandra.Client类中添加一个方法,并使用TServiceClient.sendBase方法向服务器发送请求。
结论
学习如何实现 Cassandra 客户端,命令行接口是一个很好的例子,而且从现实项目中学习要好过只是在网上搜索例子。因此,要开发 Cassandra 客户端的话,就要毫不犹豫地仔细阅读它的源代码,并享受其中的乐趣。
关于作者
Dane Dennis是 JArchitect 产品经理。他为 CoderGears 工作,这是一家为开发人员和架构师开发工具的公司。
查看英文原文:**** Cassandra CLI Internals Using JArchitect
评论