1). cassandra任何一個節點都可以被客戶端訪問。
2). 對cassandra某個節點的訪問是通過調用org.apache.cassandra.service.Cassandra的內部類Client的相 應接口實現的。
3). 2)中的Cassandra這個類包含了很多內部類和一個接口(Iface)。其中的Client和Processor兩個內部類都是對Iface的實 現,這保證了他們的內部的所有方法是一一對應的。
4). 當Client這個內部類中的某個方法被調用的時候,該方法會用他內部的send_開頭的方法發送消息,並且用recv_開頭的方法接收返回的內容,容, 返回的內容可能是我們想要的數據,也可能是個異常的消息,如果是異常的消息,則會在客戶端生成一個相應的異常並拋出,
5). Client端send_和recv_方法同目標節點的交互是分別通過oprot和iprot的實例完成的,這兩個實例是負責輸入輸出的,具體的功能的實 現在libthrift.jar中。
6). 節點和客戶端通信的連接是由libthrift.jar中的TThreadPoolServer的實例實現的,這個實例在該節點最初啓動的時候被生成,並 且該實例內部還保有一個2)中提到的Processor實例。TThreadPoolServer實例給Processor實例提供了輸入輸出實例 iprot和oprot,並且通過調用Processor的processprocess(TProtocol iprot, TProtocol oprot)接口來進一步的向內傳遞消息。
7). 節點最初啓動的初始話過程是在org.apache.cassandra.service.CassandraDaemon的setup()中完成的。
8). 在Processor的processprocess(TProtocol iprot, TProtocol oprot)會解析iprot中傳入的客戶端的請求,並首先解析出要調用函數的函數名字,然後通過查詢processMap_來決定究竟由那個 ProcessFunction實例來接收處理消息,相應的ProcessFunction實例的process(int seqid, TProtocol iprot, TProtocol oprot)被激活並開始全權負責消息的處理和反饋。
9). 相應的的ProcessFunction的實例主要負責三件事:i,進一步處理iprot傳入的消息 ii,將詳細的信息轉發給iface的相應方法處理 iii,將得到的反饋通過oprot返回給客戶端。這裏的iface實例實際上是 org.apache.cassandra.service.CassandraServer的一個實例,在Processor的實例創建的時候(節點啓 動的時候)被裝入了Processor實例,但是由於ProcessFunction類是Processor的內部類,所以 ProcessFunction的實例也能直接訪問。
10)以上可知,最終客戶端的信息是交給CassandraServer的相應方法來處理的,而thrift的相關功能只是負責了客戶端和節點間的 交互(9160端口),而節點之間的交互並沒有使用thrift的資源。
源碼中對節點的如下稱呼應該是等價的: end point , node , machine , datacenter , host。
cassandra節點的啓動main()在類org.apache.cassandra.service.CassandraDaemon中,細節在 setup()中。過程中會start一個CassandraServer的實例peerStorageServer。 peerStorageServer在建立的時候,內部會實例化一個 StorageService實例,在該StorageService實例初始化的過程中,該節點的所有功能服務會被配置激活,這些操作是在 StorageService的默認構造器中完成的。
StorageService的構造器中大致做了如下幾件事情:
1)生成一個storageLoadBalancer_s實例負責負載均衡,現在還沒有明白原理。
2)生成一個endPointSnitch_實例,這個提供了對兩個end_point進行比較的一個途徑,基本上是判斷兩個end_point是不是同 一個ip等
3)啓動了MessagingService,並且註冊了一些handler實例。MessagingService是負責該end_point與其他 end_point進行通信的。兩個節點間通信的內容被封裝在一個Message的實例裏面。
比如,如果節點A想向節點B獲得一定的數據,那麼A需要通過自己的MessageService向節點B發送一個Message實例,這個實例裏面包含了 如下信息:這個請求的類型(屬於什麼stage) , 這個請求要調用的B的哪個handler來處理,以及這個請求的其他具體內容。當節點B接收到節點A發送過來的Message實例後,會將根據這個 Message實例內部指定的handler信息,將該實例轉發相應的handler去處理。當然這樣做的前提是這個指定的handler已經在B節點注 冊了,而這個註冊過程就是在StorageService啓動的時候完成的。
4)consistencyManager_:還沒明白什麼意思。
5)StageManager的配置:
這個stage的概念來自於“SEDA“( staged event driven architecture)中的"S",參考http://www.eecs.harvard.edu/~mdw/papers/seda- sosp01.pdf。
大致意思好像是可以將一個工作流程分爲若干個階段(stage),然後給各個stage動態的分配線程資源來處理stage內定義的邏輯。stage和 stage之間的通信是通過任務隊列完成的,當一個stage的邏輯執行完後,如果需要調用下一個stage來繼續執行,那麼就往下一個stage保有的 任務隊列中寫入必要的任務信息。
這樣的SEDA結構的好處是:在實際的運行當中各個stage的忙閒程度是不一樣的,可以通過將比較閒的stage上的線程資源分配給同期比較忙的 stage來實現效率上的提高。
在cassandra中,還是以3)中例子說明,在A節點發往B節點的Message實例中有一個“這個請求的類型”的信息,這個信息保存在 Message實例內header實例的type_上,是一個字符串。這個字符串標明瞭當MessageService獲取了Message實例後究竟由 那個 stage來負責執行指定的handler對象。因爲handler對象只是定義了對Message的處理邏輯,所以需要stage裏面的線程來對其進行 執行。
當前的stage只有4種,依次在StroageService的 /* All stage identifiers */標註下被定義,分別負責不同的任務,“ROW-READ-STAGE”是負責讀取的,其他的含義還沒有完全搞清楚,大概是負責修改,數據壓縮和 http後臺頁面信息接收的。
StageManager部分的源碼還沒喲看到,可能是負責各個stage間線程調度的吧。
6)設置nodepicker_定義了當前節點對周圍節點的查詢策略,具體的還不清楚
當某個end
point拿到一個key(比如"王老六")並想取出他的相關信息的時候,這個節點是怎麼知道這個key的相關信息是存放在哪些節點中的呢?
以下將用從客戶端拿到的"get_clomun"請求爲例,進行說明:
"get_column"的相關信息會在CassandraServer的get_column(String tablename,
String key, String
columnPath)方法中被封裝成一個readCommand實例,該對象簡單包含了請求信息,另外也提供了一些別的方法。
然後該command實例會最終被交給
StorageProxy.readProtocol(command,StorageService.ConsistencyLevel.WEAK)來
處理,並期待這個方法能返回一個和key關聯的columnFamily的數據,這些數據被裝在一個row對象裏返回的。
在上述的StorageProxy.readProtocol(...)這個方法中,首先要做的事情就是根據提交的key("王老六")確定他的相關信息
內容是存放在那些end point中的。而這個尋找end
point的工作由StorageService.instance().getNStorageEndPoint(command.key)來完成。
實際上,上述的StorageService的getNStorageEndPoint(String
key)方法調用了他持有的實例"nodePicker_"的getStorageEndPoints(Token token) 來完成尋找end
point這件事情。
題外話:StorageService這個實例提供了本地節點數據存儲和與其他節點交互的服務,這個實例是在節點啓動之初被建立的,在建立的時候該實例會
初始化一個nodePicker_放在其內部,這個nodePicker主要負責本地節點同其他節點的交互規則,也就是如何選擇其他節點的策略。當前的策
略有兩種:RackAwareStrategy--機架敏感 和
RackUnawareStrategy--非機架敏感,默認策略是RackUnawareStrategy。
所以默認情況下,nodePicker_就是一個RackUnawareStrategy實例,而他的
getStorageEndPoints(Token token)將被用來查找合適我們給出的key("王老六")的end
point,這裏合適的意思是“存有關於這個key的信息”。而這裏提到的token是由key封裝得來的,他對key進行了hash,並且繼承了
Comparable接口。也就是說,這個token實例是可以和其他同類token比較大小的,而這樣做的目的是爲了“可以在Token組成的list
上進行二分查詢Collection.binarySort,並定位查找目標的在list中的相對位置”--這種查找操作將被經常使用。默認情況下
token是一個BigIntegerToken的實例。
nodePicker_最終會將查找的工作交給getStorageEndPoints(Token token,
Map<Token, EndPoint>
tokenToEndPointMap)來做,這個方法會返回一個查找到的end_point的數組。以下是對RackUnawareStrategy中
的相應方法的說明(因爲默認是使用這個方法):
//此處的tokenToEndPointMap保存了該節點知道的所有其他節點,並且可以根據他們的token查找到
//此處的token是我們要查找的key("王老五")生成的token,他跟上面end
point的token生成的算法是相同的,也都是BigIntegerToken
public EndPoint[] getStorageEndPoints(Token token, Map<Token,
EndPoint> tokenToEndPointMap)
{
//根據key尋址的切入點
int startIndex;
List<EndPoint> list = new ArrayList<EndPoint>();
int foundCount = 0;
//將key的set裝入一個ArrayList中,並完成排序,(一位token是comparable的)
List tokens = new
ArrayList<Token>(tokenToEndPointMap.keySet());
Collections.sort(tokens);
//在排好序的tokens中二叉樹搜索token,並返回token所在的位置的index
//個人認爲:根本別指望tokens裏面能有我們要找的token,因爲一個是end
point的token集合,一個是查找key("王老六")生成的
//token,生成的時候都使用了hash,並且加入了大隨即數,重合的概率很低。
//這樣做的主要目的是爲了獲得下面這個index,(參見binarySearch的說明可知)。
//也就是:雖然下面這條語句不大可能從tokens中取出東西,但是生成的index將告訴我們,哪個end
point的token離我們給出的
//key"王老六"的token“最近”。而且,在節點情況固定下來的情況下,用當前這種方法,
//key"王老六"的token確定的對應的end point也是不會改變的(每次都會取到這個end point,除非end
point本身註冊的變了)。
//也就是說:這就成了一種方法,一種能夠根據數據key("王老六")定位到一個end point的方法,
//如果寫數據時使用了這種策略找到一個end point
節點然後寫進數據,那麼手持相應的key在讀數據的時候,同樣使用這個策略,也
//相應的能找到當時存入數據的那個節點,並把數據讀出來。
int index = Collections.binarySearch(tokens, token);
//基本上這個條件裏面的語句肯定會被執行
//那如果真的用key("王老六")從tokens中找到對應了呢?這個方法裏的策略就是,那就直接拿這個被確定的節點來當
“鄰居”節點了
//而key的tokens和end_point的token之所以能混在一起說話,是因爲他們都是BigIntegerToken,在同一個數據空間中
if(index < 0)
{
//以下的運算將把key("王老六")的"鄰居"節點給"翻譯"出來
index = (index + 1) * (-1);//得到可以插入的位置
if (index >= tokens.size())
index = 0;
}
int totalNodes = tokens.size();
// Add the node at the index by default
//把我們找到的"鄰居"節點放到一個要返回的list中,當然,對於一個key("王老六")的信息還可能在其他節點上存有副本(replicas)
//再下面的操作會將這些存有副本的節點也一同取出
list.add(tokenToEndPointMap.get(tokens.get(index)));
foundCount++;
//本來index是從0...一直到size()的一條線,如果我們用如下的方式選取index,就好似將所有的index數值放在了一個圓圈上,然後
按
//從小到大的方向在選取下一個index。startIndex就是一個切入點,本質上Partitioner提供了這個可被"切入"的結構和線索
//猜想這也是爲什麼叫Partitioner的原因吧
startIndex = (index + 1)%totalNodes;
// If we found N number of nodes we are good. This loop will
just exit. Otherwise just
// loop through the list and add until we have N nodes.
// replicas_對應storage-conf.xml中的最大副本數量
//將存有副本的節點也一同取出,就是最初"鄰居"節點往後的若干個節點,具體若干幾個是在storage-conf.xml的
//<ReplicationFactor>標籤中定義的
for (int i = startIndex, count = 1; count < totalNodes
&& foundCount < replicas_; ++count, i = (i+1)%totalNodes)
{
if( ! list.contains(tokenToEndPointMap.get(tokens.get(i))))
{
list.add(tokenToEndPointMap.get(tokens.get(i)));
foundCount++;
}
}
//統一下節點監聽端口的信息
retrofitPorts(list);
return list.toArray(new EndPoint[list.size()]);
}
(一)依賴:cassandra.jar
libthrift.jar
(二)連接:
//該方法將返回一個Cassandra.Client實例,該實例包含和server端指定節點會話的API
public Cassandra.Client getClient()
{
//192.168.0.169爲想連接到的某個節點的ip,9160爲端口
TSocket socket = new TSocket("192.168.0.169", 9160);
TTransport transport_ = socket;
TBinaryProtocol binaryProtocol = new TBinaryProtocol(transport_,
false, false);
Cassandra.Client cassandraClient = new
Cassandra.Client(binaryProtocol);
try
{
transport_.open();
}
catch(Exception e)
{
// Should move this to Log4J as well probably...
System.err.println("Exception " + e.getMessage());
e.printStackTrace();
}
return cassandraClient;
}
(三)API
1)該方法向key的指定path插入一個數據,另外想要對key某指定path的數據進行修改也使用這個方法
代碼中的cli是一個Cassandra.Client實例
public void test_insert()
{
try {
String tableName = "Table1";
String key = "testkey";
String columnFamily = "Standard1";
String columnName = "testColumn";
String value = "testValue";
//對數據進行定位
String path = columnFamily+":"+columnName;
//向cassandra中插入一條數據
cli.insert(tableName,key,path,value.getBytes(),System.currentTimeMillis(),true);
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
} catch (UnavailableException e) {
e.printStackTrace();
}
}
2)該方法試用Cql語句的方式發送請求,有一個問題就是當請求的參數或不正確的時候,方法不會報錯,只是返回爲空;
但是在語法出現錯誤的時候還是會有InvalidRequestException拋出,提示Unresolved compilation
problems
代碼中的cli是一個Cassandra.Client實例
public void test_excuteQuery()
{
try {
cli.executeQuery("set
Table1.Standard1['testKey2']['testColumn']='testValue2'");
CqlResult_t crt = cli.executeQuery("get
Table1.Standard1['testKey2']");
List<Map<String, String>> rs =
crt.getResultSet();
//遍歷顯示所有column_t中的內容
if(rs != null){
for(int i = 0 ; i < rs.size() ; i++){
Map<String , String> map = rs.get(i);
for(String key : map.keySet()){
System.out.println(key+" : "+map.get(key));
}
}
}else{
System.out.println("result set is null");
}
} catch (TException e) {
e.printStackTrace();
}
}
3)根據path信息獲取一個column
path是一個定位信息,standard column family是兩層 super column family是三層
superColumnFamily:superColumn:standardColumn。
public void test_get_column()
{
String tableName = "Table1";
String key = "testKey2";
String columnFamily = "Standard1";
String column = "testColumn";
String path = columnFamily+":"+column;
try {
column_t cmt = cli.get_column(tableName, key, path);
//以下兩個是等價的
System.out.println(cmt.getColumnName());
System.out.println(cmt.getFieldValue(1));
//以下兩個是等價的
System.out.println(new String(cmt.getValue(),"UTF-8"));
System.out.println(new String((byte[])
cmt.getFieldValue(2),"UTF-8"));
//以下兩個是等價的
System.out.println(cmt.getTimestamp());
System.out.println(cmt.getFieldValue(3));
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
4)
//術語解釋:每個key都對應一個Row的信息這個Row的信息又按照columnFamliy 分成了很多slice
//給定table,key和columnFamily的信息,
//此方法將返回,該key在當前columnFamily的slice中有都少個column元素
//該例子中key:"testKey2"在columnFamily:"Standard1"中的slice有幾個column元素
public void test_get_column_count()
{
String tableName = "Table1";
String keyName = "testKey2";
String columnFamily = "Standard1";
try {
System.out.println(cli.get_column_count(tableName, keyName,
columnFamily));
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
5) //暫不確定
//根據給定的時間點,取出以後的對應給定key的一組columns
//但是好像在時間上有延遲,這個不確定
public void test_get_columns_since()
{
String tableName = "Table1";
String keyName = "testKey2";
String columnFamily = "Standard1";
Long sinceTime =
System.currentTimeMillis()-5000*1000;//取當前時間之前120秒以內的
try {
List<column_t> cmts = cli.get_columns_since(tableName ,
keyName , columnFamily , sinceTime);
System.out.println("cmts size : "+cmts.size());
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
6)//range queries may only be performed against an order-preserving
partitioner
//如果系統試用的節點定位的RandomPartitioner的話那麼這個方法將不能試用,好處是跟均衡的存儲
//此方法尚在研究中
public void test_get_key_range()
{
try {
cli.get_key_range("Table1", "??", "??", 1);
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
7) //術語解釋:每個key都對應一個Row的信息這個Row的信息又按照columnFamliy 分成了很多slice
//每一個columnFamily都存在一個排序,或按照name或按照time
//該方法將取出對應某個key的一定區間段內的column元素
public void test_get_slice()
{
String tableName = "Table1";
String keyName = "testKey2";
String columnFamily = "Standard1";
//想要從哪個columnFamily中獲取column
int start = 0;//起始位置
int count = 10;//獲取數量
try {
List<column_t> cmts = cli.get_slice(tableName,
keyName, columnFamily, start, count);
System.out.println("cmts size : "+cmts.size());
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
8)//通過一個column的名字數組來查找相對應的屬於給定key的slice(一組column 元素)
public void test_get_slice_by_names()
{
String tableName = "Table1";
String keyName = "testKey";
String columnFamily = "Standard1";
List<String> columnNameList = new
ArrayList<String>();
columnNameList.add("testColumn");
try{
List<column_t> cmts =
cli.get_slice_by_names(tableName, keyName, columnFamily,
columnNameList);
}catch(Exception e){
e.printStackTrace();
}
}
9)//描述一個table中的各個columnFamily的基本信息
public void test_describe_table()
{
try {
System.out.println(cli.describeTable("Table1"));
} catch (TException e) {
e.printStackTrace();
}
}
10)//批量的針對一個key插入數據
public void test_batch_insert()
{
try{
String tableName = "Table1";
String keyName = "testkey2";
//當下的Map保存了ColumnFamily到他內部的column元素列表的一個映射
//所以String 應該保存 對應columnFamily的名字。
//column_t(column元素)包含:columnName , value , timestamp 三要素
Map<String , List<column_t>> CFmap = new
HashMap<String , List<column_t>>();
//製作兩個column_t實例
column_t cmt1 = new column_t("test_column_name_1" ,
"test_column_value_1".getBytes() , System.currentTimeMillis());
column_t cmt2 = new column_t("test_column_name_2" ,
"test_column_value_2".getBytes() , System.currentTimeMillis());
//製作一個column_t的list
List<column_t> cList = new
ArrayList<column_t>();
cList.add(cmt1);
cList.add(cmt2);
//製作一個將其寫入CFmap中
String columnFamilyName = "Standard1";
CFmap.put(columnFamilyName,
cList);//這樣在以後的插入操作中,相應的column_t將插入"Docin"這個family中
batch_mutation_t bmt = new batch_mutation_t(tableName ,
keyName , CFmap);//完成插入的key的定位
//執行這個針對相應key和colmunFamily的batch_insert
cli.batch_insert(bmt, true);
}catch(Exception e){
e.printStackTrace();
}
}
11)//同insert()相對的反向操作
//當我們試圖查詢一個不存在的column_t元素的時候,會有"NotFoundException"異常被拋出
public void test_remove()
{
String tableName = "Table1";
String columnFamily = "Standard1";
String columnName = "removetest";
String keyName = "removetestkey";
String value = "removetestvalue";
String path = columnFamily+":"+columnName;
try{
//先將相應的column元素寫入
cli.insert(tableName, keyName, path, value.getBytes(),
System.currentTimeMillis(), true);
//嘗試讀出信息
column_t cmt = cli.get_column(tableName, keyName, path);
displayColumn(cmt);
//刪除這條信息
cli.remove(tableName, keyName, path,
System.currentTimeMillis(), true);
}catch(Exception e){
e.printStackTrace();
}
//嘗試讀出信息
column_t cmt2;
try {
cmt2 = cli.get_column(tableName, keyName, path);
displayColumn(cmt2);
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (NotFoundException e) {
System.out.println("NotFoundException 異常拋出,column_元素已經被刪除");
} catch (TException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
12)//插入一個superColumn元素
//不含有內容的superColumn不能同過該方法被創建,會拋出異常
public void test_batch_insert_super_t()
{
try {
String tableName = "Table1";
String keyName = "superkeytest";
String superColumnFamilyName = "Super1";
//產生1個superColumn元素
//首先產生1個column_t的list
List<column_t> Clist = new
ArrayList<column_t>();
//當兩個名字一樣的時候,先前的會被覆蓋
Clist.add(new
column_t("stardcolumninsuper1","stardcolumn1".getBytes(),System.currentTimeMillis()));
Clist.add(new
column_t("stardcolumninsuper2","stardcolumn2".getBytes(),System.currentTimeMillis()));
//產生一個superColumn元素
String superColumnName = "superColumn3";
superColumn_t sct = new superColumn_t(superColumnName ,
Clist);
//將這個元素裝入到一個superColumn_t的List中
List<superColumn_t> SClist = new
ArrayList<superColumn_t>();
SClist.add(sct);
//將這個super_column
的list連同對應的superColumnFamily的名字一起放入一個hashmap中
Map<String , List<superColumn_t>> cfmap = new
HashMap<String , List<superColumn_t>>();
cfmap.put(superColumnFamilyName, SClist);
//區別於一般的,這裏要求的column元素是superColumn元素
//將整理好的信息關聯一個key後生成一個mutation
batch_mutation_super_t bmst = new
batch_mutation_super_t(tableName,keyName,cfmap);
boolean block = true;
//執行這個mutation的插入操作
cli.batch_insert_superColumn(bmst, block);
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (UnavailableException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
13)//獲取一個SuperColumn中的信息
public void test_get_superColumn()
{
String tableName = "Table1";
String keyName = "superkeytest";
String superColumnFamilyName = "Super1";
String superColumnName = "superColumn3";
String path = superColumnFamilyName+":"+superColumnName;
try {
superColumn_t sct = cli.get_superColumn(tableName, keyName,
path);
if(sct != null){
System.out.println("Super Name : "+sct.getName());
System.out.println("Size : "+sct.getColumnsSize());
List<column_t> clist = sct.getColumns();
}else{
System.out.println("super column is empty");
}
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
14)//嘗試向一個superColumn中添加column元素(就是column_t對象)
//注意:想superColumnFamily中添加一般的column元素是不被允許的,所以superColumnFamliy中只能有
superColumn,而superColumn中只能有一般的column
//而一般的columnFamily中也只能有一般的column
public void test_insert_standard_to_super()
{
String tableName = "Table1";
String keyName = "superkeytest";
String superColumnFamily = "Super1";
String superColumnName = "superColumn1";
String columnName = "normal_column";
String value = "normal_column_value_add";
String path =
superColumnFamily+":"+superColumnName+":"+columnName;
try {
cli.insert(tableName, keyName, path,
value.getBytes(),System.currentTimeMillis(), true);
//嘗試取出這個元素並顯示這個元素
column_t cmt = cli.get_column(tableName, keyName, path);
if(cmt != null){
System.out.println("column name :
"+cmt.getColumnName());
System.out.println("column value: "+new
String(cmt.getValue(),"UTF-8"));
System.out.println("column time :
"+cmt.getTimestamp()+"/n");
}else{
System.out.println("column is empty");
}
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (UnavailableException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
15)//術語解釋:每個key都對應一個Row的信息這個Row的信息又按照columnFamliy
或superColumnFamily分成了很多slice
//同針對一般的columnFamliy類似,這個方法是獲取給定key的某個superColumnFamily的slice,這個slice中的
元素都是superColumn
public void test_get_slice_super()
{
String tableName = "Table1";
String keyName = "superkeytest";
String superColumnFamily = "Super1";
try {
List<superColumn_t> sct =
cli.get_slice_super(tableName, keyName, superColumnFamily, 0, 10);
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
16)
//嘗試刪除一個superColumn節點,和屬於該節點的column_t節點
//測試需要通過path來定位這個節點
public void test_remove_super()
{
String tableName = "Table1";
String keyName = "superkeytest";
String superColumnFamily = "Super1";
String superColumn = "superColumn3";
String columnName = "stardcolumninsuper1";
String path = superColumnFamily+":"+superColumn+":"+columnName;
try {
//先建立一個super_column_family 名字測試的時候是"superColumn3"
//產生1個superColumn元素
//首先產生1個column_t的list
List<column_t> Clist = new
ArrayList<column_t>();
//當兩個名字一樣的時候,先前的會被覆蓋
Clist.add(new
column_t("stardcolumninsuper1","stardcolumn1".getBytes(),System.currentTimeMillis()));
Clist.add(new
column_t("stardcolumninsuper2","stardcolumn2".getBytes(),System.currentTimeMillis()));
//產生一個superColumn元素
String superColumnName = "superColumn3";
superColumn_t sct = new superColumn_t(superColumnName ,
Clist);
//將這個元素裝入到一個superColumn_t的List中
List<superColumn_t> SClist = new
ArrayList<superColumn_t>();
SClist.add(sct);
//將這個super_column
的list連同對應的superColumnFamily的名字一起放入一個hashmap中
Map<String , List<superColumn_t>> cfmap = new
HashMap<String , List<superColumn_t>>();
cfmap.put(superColumnFamilyName, SClist);
//區別於一般的,這裏要求的column元素是superColumn元素
//將整理好的信息關聯一個key後生成一個mutation
batch_mutation_super_t bmst = new
batch_mutation_super_t(tableName,keyName,cfmap);
boolean block = true;
//執行這個mutation的插入操作
cli.batch_insert_superColumn(bmst, block);
//嘗試刪除之
cli.remove(tableName, keyName, path,
System.currentTimeMillis(), true);
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (UnavailableException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
17)//這個方法目前還不健全
public void test_getProperty()
{
try {
//這事一個未健全的方法,現在只能返回對應"tables"的參數,也就是表名
List<String> Plist =
cli.getStringListProperty("tables");
for(int i = 0 ; i < Plist.size() ; i++){
System.out.println(Plist.get(i));
}
} catch (TException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//現在只支持如下三個參數
try {
System.out.println(cli.getStringProperty("cluster name"));
//會直接把server端的storage_conf.xml讀進來,可能用於做進一步的xml分析
System.out.println(cli.getStringProperty("config file"));
System.out.println(cli.getStringProperty("version"));
} catch (TException e) {
e.printStackTrace();
}
}
一些注意的:
1)cli的實例還有一些諸如sent_*和recv_*(成對出現)的公共方法,這些方法是供上面羅列的相應方法調用的,一般不用管,
2)sent_*負責想服務器端發送消息
3)recv_*將處理返回的所要的結果或處理錯誤信息拋出相應異常
4)大部分方法可執行的前提是必須有一個精確的key信息,(describe_table和get_key_range除外)。
5)一個superColumnFamily中只能存放superColumn_t元素而不能存放column_t元素
6)一個columnFamily中只能存放column_t元素
1)columnFamily下一個column和多個column的讀取區別
2)columnfamily 和superColumnFamily的讀取區別
測試機數量:兩臺,jvm最大使用內存都開到1.3G。
起始key: 1356278962 ;
改變組:
product_name1 : "是一個非常可靠的大規模分佈式存儲系統"
product_name2 : "中國慘敗伊朗丟亞錦賽冠軍創34年參賽最恥辱一敗"
控制組:
product_value1 : "第一次在主場丟冠軍"
product_value2 : "胡雪峯頂替受傷的劉煒"
product_value3 : "朱芳雨都有外線出手機會"
product_value4 : "張慶鵬傳球意圖太過明顯"
product_value5 : "最後一節比賽"
product_value6 : "中國隊首發:易建聯、王治郅、朱芳雨、王仕鵬、胡雪峯"
product_value7 : "中國隊本次亞錦賽首次嚐到失利的滋味,在家門口把冠軍拱手相讓"
product_value8 : "下半場易邊再戰,中國隊仍然如同夢遊,球員之間沒有形成整體"
//下面這個是一個要存入的“摘要”
product_value9 : "下半場易邊再戰,中國隊仍然如同夢遊,球員之間沒有形成整體,
單打獨鬥的進攻模式成功率相當低。伊朗隊內外結合,多點得分,
繼續擴大分差。朱芳雨傳球被斷,對手長傳快攻,14號球員扣籃得分,
28-53。王治郅強攻得手,造成哈達迪犯規,加罰命中。
王治郅再次溜到籃下,反身投籃得分。王治郅成爲中國隊的唯一亮點,
持球接連晃過三名球員的防守,投籃得分,加罰再中,
36-53。靠着王治郅的出色發揮,中國隊留住翻盤的一線希望。
第三節結束,39-56,分差仍爲17分。"
我們有的ColumnFamily: Standard1 Standard2 Super1 Super2
測試1:
向Standard1中寫入10萬條記錄,此時Standard1中只有"product_name"一個column,key是從1356278962
開始往後10萬個
insert()進行插入,速度很慢,不可能在一小時內完成
CQL 用時256秒,關掉log後用時55秒
測試2: 然後在product_name1 和
product_name2之間反覆修改key-1356279962對應的"produck_name"這個column的內容10萬次
CQL:
當config中的ReplicationFactor爲1的時候,用時120秒
當config中的ReplicationFactor爲2的時候,用時183秒;關掉log後用時42秒
測試3: 將存在的各個key的product_name讀取一遍(此時相應columnFamily中只有1個column)
當config中的ReplicationFactor爲1的時候:速度相當慢
當config中的ReplicationFactor爲2的時候:
CQL:
用時267秒;關掉log後用時145秒
API:
用get_column用時203秒;關掉log後用時135秒
以下均以ReplicationFactor=2來進行測試
測試4: 依照已經存在的各個key,向Standard1中寫入10萬條控制組裏面的column數據
API:
batch_insert() 速度非常慢
CQL:
90萬個column元素,其中包括一個類似摘要大小的字符串(200字左右),用時2004秒;關掉log後用時512秒。
測試5: 然後在product_name1 和
product_name2之間反覆修改key-1356279962對應的"produck_name"這個column的內容10萬次
API:
使用insert()速度相當慢
CQL:
用時190秒;關掉log後用時45秒
測試6: 將存在的各個key的product_name讀取一遍(此時相應columnFamily中已經有10個column了)
API:
使用get_column()用時757秒;關掉log後用時188秒
CQL:
用時更慢一些,用時861秒;關掉log後用時202秒
super測試1:在Super1中創建superColumn1,在superColumn1中只創建1個
column-"product_name",然後寫入10萬條數據,key是從1356278962開始往後10萬個
CQL:
用時316秒;關掉log後用時60秒
super測試2:然後在product_name1 和
product_name2之間反覆修改superColumn1下key-1356279962對應的"produck_name"這個column的
內容10萬次
CQL:
用時195秒;關掉log後用時62秒
super測試3:將存在的各個key的product_name讀取一遍(此時相應superColumnFamily中只有1個column)
API:
get_column() 用時235秒;關掉log後用時234秒
CQL:
用時356秒 ;關掉log後用時158秒
super測試4:依照已經存在的各個key,依次向superColumn1中寫入10萬條控制組裏面的column數據
CQL:
用時2058秒;關掉log後,竟然非常慢,不知道爲什麼,但是重新啓動下節點後,速度變快,用時562秒
super測試4:然後在product_name1 和
product_name2之間反覆修改superColumn1下key-1356279962對應的"produck_name"這個column的
內容10萬次
CQL:
用時190秒,關掉log後用時48秒
super測試6:將存在的各個key的product_name讀取一遍(此時相應superColumnFamily中已經有10個column了)
API:
用get_column 用時 356秒;關掉log後用時191秒
CQL:
用時314秒;關掉log後用時208秒
結論:使用superColumn的開銷不比column大很多,向columnFamily中加入更多的column對讀取速度的影響不大。相比 而言受log和ReplicationFactor的影響更大。另外在寫入的時候CQL的效率更高,在讀取的時候get_column更好一些。而API 中的其他方法如batch_insert(),insert()效率明顯不高。但這只是兩個節點的情況,當有更多節點的時候情況可能又會不一樣了。