說明
在上一篇文章dotnetcore 與 hbase 之二——thrift 客戶端的制作中已經可以找到 c# hbase 客戶端的使用方法了,為什么這里單獨列出一篇文章來講述呢?最簡單的理由就是,本篇將為客戶端的使用講述一些簡化性工作以及需要注意的事項。為此,我們做了一些工作HbaseNetCore。
存在的差異
在 c# hbase 客戶端中,接口基本只接受或返回 byte 數組型的參數,比如:
Task<List<byte[]>> getTableNamesAsync(CancellationToken cancellationToken); # 獲取所有hbase中的表名
Task<List<TRowResult>> getRowAsync(byte[] tableName, byte[] row, Dictionary<byte[], byte[]> attributes, CancellationToken cancellationToken); # 根據rowkey獲取一行數據
所以,大多數的工作將是 customer's class 與 byte array 之間的轉換。需要注意的事項正是來源於 c#與 java 的一些差異。
1. byte 數組大小端
在我的工作中遇到的 java 主要使用類org.apache.hadoop.hbase.util.Bytes
來進行二進制化(例如:Bytes.toBytes(/**/)
),以此來達到和 hbase 進行數據交換的目的。在 Java 中 byte 數組與網絡字節流采取一致的大小端模式——大端模式。而在 c#中,主要使用類System.BitConverter
來獲取二進制流,得到的 bytes 結果是小端模式的。因此,為了通用,我們需要使用Array.Reverse()
方法來進行大小端反轉。也可以使用System.Net.IPAddress.HostToNetworkOrder()
來直接獲取網絡字節順序的 bytes。但是其結果不是 byte[],仍然需要使用System.BitConverter
來獲取 byte[]。此外,單字節編碼的字符串(比如:ASCII 、utf-8)不存在大小端的問題,雙字節及以上需要注意大小端(比如:utf-16、GB2312)。
2. 基本值類型的對應
在 Java 中,基本數據類型(byte、short、int、long、float、double、char、boolean)中不存在無符號型。而在 c#中(bool、byte、char、decimal、double、float、int 32、long、sbyte、short、uint、ulong、ushort)有無符號型。所以在使用數據類型上需要注意對應。此外,二者在布爾類型上也有差異。從轉換的 byte[]中可以看出來:
//java
true:[-1]
false:[0]
//c#
true:01 # 幸運的是所有非零值轉bool都是true。BitConverter.ToBoolean(BitConverter.GetBytes((sbyte)-1))
false:00
在 java 中,short 是 4 字節的,而 c#中僅為 2 字節:
//java
System.out.println("short:" + Arrays.toString(Bytes.toBytes('d')));
short:[0, 0, 0, 100] # 十進制
//c#
Console.WriteLine($"short:{BitConverter.ToString(((short)'d').ToBytes())}");# 注意:方法ToBytes獲取了bytes並反轉了大小端
short:00-64 # 十六進制
customer's class 與 byte array 之間的轉換
為了提高工作效率,需要做一些轉換工具,以此來減少 byte[]與用戶數據之間的徒手轉換工作。
1. 為 hbase 倉儲而創建的特性與接口
特性類 HbaseTableAttribute 構造函數接受一個可選參數,以此指明與 hasee 對應的表名。
public HbaseTableAttribute(string table = null)
特性類 HbaseColumnAttribute 構造函數接受兩個可選參數,以此指明列簇名與列名
public HbaseColumnAttribute(string family = DefaultFamily, string column = null)
接口 IHbaseTable 強制提供 byte[]轉換為用戶類時提供的 RowKey 屬性。
public interface IHbaseTable
{
string RowKey { get; set; }
}
舉一student
類為例:
[HbaseTable("student")]
public class Student : IHbaseTable
{
public string RowKey { get; set; }
[HbaseColumn]
public string Name { get; set; }
[HbaseColumn]
public int Age { get; set; }
[HbaseColumn]
public bool IsWork { get; set; }
public DateTime JoinSchool { get; set; }
[HbaseColumn]
public List<string> Hobbies { get; set; }
}
2.為 c# hbase 客戶端與用戶類之間轉換創建的輔助工具類
目前實現的轉換接口有:
public interface IHbaseParser
{
//用戶類轉 hbase 客戶端
List<Mutation> ToMutations<T>(T obj) where T : class;
//用戶類轉 hbase 客戶端
BatchMutation ToBatchMutation<T>(T obj) where T : class, IHbaseTable;
//用戶類集合轉 hbase 客戶端
List<BatchMutation> ToBatchMutations<T>(IEnumerable<T> objs) where T : class, IHbaseTable;
// hbase 客戶端轉用戶類
T ToReal<T>(TRowResult trr) where T : class, IHbaseTable, new();
// hbase 客戶端轉用戶類集合
List<T> ToReals<T>(IEnumerable<TRowResult> trrs) where T : class, IHbaseTable, new();
}
幫助性接口:
public interface IHbaseHelper
{
//獲取特性HbaseTableAttribute中的表名
string GetTableName<T>() where T : class, new();
//獲取特性HbaseColumnAttribute中的列簇名
List<string> GetTableColumnNames<T>() where T : class, new();
}
使用方式如:
//c# hbase 客戶端准備
var _clientTransport = new TSocketClientTransport(IPAddress.Loopback, 9090);
var protocol = new TBinaryProtocol(_clientTransport);
var _client = new Hbase.Client(protocol);
var _hbaseHelper = new HbaseHelper();
var _HbaseParser = new HbaseParser();
var cancel = new CancellationToken();
//在hbase創建表
var table = _hbaseHelper.GetTableName<Student>();
var colNames = _hbaseHelper.GetTableColumnNames<Student>();
var columnFamilies = colNames
.Select(t => new ColumnDescriptor { Name = t.ToBytes() })
.ToList();
await _client.createTableAsync(table.ToBytes(), columnFamilies, cancel);
//寫入1000條數據
var range = Enumerable.Range(0, 1000).ToList();
var students = range
.Select(t => new Student { RowKey = t.ToString().Reverse2String(), Name = $"hsx{t}", Age = t })
.ToList();
students.Last().Hobbies = new List<string> { "running", "dance" };
students.Last().IsWork = true;
var batchs = _HbaseParser.ToBatchMutations(students);
await _client.mutateRowsAsync(table.ToBytes(), batchs, null, cancel);
//從‘0’開始掃描數據
var id = await _client.scannerOpenAsync(
table.ToBytes(),
"0".ToBytes(),
null,
null,
cancel);
students = new List<Student>();
List<Student> perStudents = null;
do
{
var tRows = await _client.scannerGetListAsync(id, 500, cancel);
perStudents = _HbaseParser.ToReals<Student>(tRows);
students.AddRange(perStudents);
} while (perStudents?.Count > 0);
總結
雖然我們可以進行 hbase 的讀寫,但是效率上的差距依然明顯,基本在幾倍的讀性能差距上,寫數據性能差異尚未測試。作為 net 了解 hbase 的一個學習窗口吧。
更多細節,請查看HbaseNetCore項目。c# hbase 客戶端基本告一段落,如有更新,也基本只會出現在本項目源代碼中。