说明:对Python操作hbase的常用操作做一个统一的封装,同时hbase的rowkey(行键)常用的存储方式是hash散列方式存储,故该工具类扩展了常用MD5加密的方式,如需要其他方式的存储请自行扩展。
Python3连接hbase问题汇总_MY测试之道的博客-CSDN博客
"""
hbase相关的操作
"""
import happybase
import hashlib
def md5_encrypt(text):
"""对字符串MD5加密,取前32位"""
md5 = hashlib.md5()
md5.update(text.encode('utf-8'))
encrypted_text = md5.hexdigest()
# 取前32位字符
encrypted_text_32 = encrypted_text[:32]
return encrypted_text_32
class HbaseUtils:
# 初始化table对象
__init_table = None
def __init__(self, bootstrap_servers=[], protocol='binary', transport='buffered', table_name=None):
"""
:function: 初始化Hbase连接
:param bootstrap_servers: Hbase服务器集群地址和Thrift服务端口, 默认端口:9090 ,eg:192.168.172.121:9090
:param protocol: 连接协议,默认为 'binary'
:param transport: 传输方式,默认为 'buffered'
:param table_name: 初始化连接的表,默认为 None
"""
self.connection_status = False
self.connection_fail_info = {}
if not isinstance(bootstrap_servers, list):
raise ValueError("参数bootstrap_servers必须是list类型!")
for addr in bootstrap_servers:
host = addr.split(":")[0]
port = 9090
if ":" in addr:
port = int(addr.split(":")[1])
try:
self.conn = happybase.Connection(host=host, port=port, protocol=protocol, transport=transport,timeout=10000)
if self.is_hbase_master:
self.connection_status = True
if table_name:
self.__init_table = self.conn.table(table_name)
print(f"初始化Hbase主节点连接成功,主节点信息:{addr}")
break
except Exception as e:
self.connection_fail_info[f"{addr}"] = e
if not self.connection_status:
raise ValueError(f"初始化连接Hbase失败!,失败原因如下:\n {self.connection_fail_info}")
@property
def is_hbase_master(self):
"""判断连接的Hbase是否为主节点地址"""
status = self.conn.is_table_enabled('hbase:meta')
if status:
return True
else:
return False
def __get_table_object(self, table_name=None):
"""根据table_name获取表连接对象"""
if table_name:
self.__init_table = self.conn.table(table_name)
table = self.__init_table
elif self.__init_table:
table = self.__init_table
else:
raise ValueError("参数table_name不能为空!")
return table
def __code_values(self, data, code_type="decode"):
"""
:function: 对列表、元祖、字典类型的数据结构里面的bytes类型数据编码成utf-8 或 将str编码成bytes
:param data: 需要操作的数据
:param code_type: 默认:decode bytes->utf-8 encode;utf-8->bytes
:return:
"""
if not data and data !=0:
log.error("参数data不可为空!")
raise ValueError("参数data不可为空!")
if isinstance(data, list) or isinstance(data, tuple):
return [self.__code_values(item, code_type) for item in data]
elif isinstance(data, dict):
if code_type == "encode":
return {key.encode(): self.__code_values(value, code_type) for key, value in data.items()}
return {key.decode('utf-8'): self.__code_values(value, code_type) for key, value in data.items()}
else:
try:
if code_type == "encode":
return str(data).encode()
return data.decode('utf-8')
except (AttributeError, UnicodeDecodeError):
return data
def __validate_input_type(self, data, data_type):
"""校验输入参数类型"""
if type(data_type) is type:
if not isinstance(data, data_type):
raise ValueError(f"参数{data}的类型必须是{data_type}!")
elif type(data_type) is list or type(data_type) is tuple:
if type(data) not in data_type:
raise ValueError(f"参数{data}的类型必须是{data_type}!")
else:
raise ValueError("参数data_type的类型错误!")
def get_tables(self):
"""获取全部表信息"""
tables_byte = self.conn.tables()
return self.__code_values(tables_byte)
def get_table_datas(self, table_name=None, limit=10, filter=None):
"""
:function: 获取指定表的数据,默认前10条数据
:param table_name: 表名 【初始化连接时指定了表名,可缺省】
:param limit: 显示的数据条数
:param filter: 过滤器, eg:"SingleColumnValueFilter('tag', 'user_tag_value', =, 'binary:1',true, true)"
【注:最后两个参数true,表示返回的结果为实际预期过滤的结果;如果没有这两个参数,则不存在列user_tag_value的数据也会返回】
:return:
"""
data_list = []
table = self.__get_table_object(table_name)
if filter and limit != 10:
data = table.scan(filter=filter, limit=limit)
elif filter:
data = table.scan(filter=filter)
else:
data = table.scan(limit=limit)
for key, value in data:
data_list.append([self.__code_values(key), self.__code_values(value)])
return data_list
def get_rows_datas(self, rowkeys, table_name=None, colums=[], rowkey_format=None):
"""
:fuction: 根据指定条件获取指定数据集
:param rowkeys: 行号
:param table_name: 表名 【初始化连接时指定了表名,可缺省】
:param colums: 列簇:列名
:param rowkey_format: rowkey的格式,如果为:None 保持原有格式;如果为:MD5 则先对rowkey进行MD5加密
:return:
"""
table = self.__get_table_object(table_name)
self.__validate_input_type(rowkeys, list)
self.__validate_input_type(colums, list)
if rowkey_format == "MD5":
rowkeys = [md5_encrypt(rowkey) for rowkey in rowkeys]
datas = table.rows(rows=rowkeys, columns=colums) if colums else table.rows(rows=rowkeys)
if datas:
return self.__code_values(datas)
def upsert_one_data(self, rowkey, data, table_name=None, rowkey_format=None):
"""
:fuction: 对指定表插入或更新单条数据 【注:如果添加的数据不存在则插入,存在则更新】
:param rowkey: 行号
:param data: 插入的列数据
eg:表只有一个列簇的情况 data = {
b'column1': b'value7',
b'column2': b'value8',
}
eg:表存在多个列簇的情况,必须指定列簇 data = {
b'cf1:column1': b'value7',
b'cf1:column2': b'value8',
}
:param table_name: 表名 【初始化连接时指定了表名,可缺省】
:param rowkey_format: rowkey的格式,如果为:None 保持原有格式;如果为:MD5 则先对rowkey进行MD5加密
:return:
"""
tmp_dict = {}
origin_rowkey_value = rowkey
table = self.__get_table_object(table_name)
self.__validate_input_type(data, dict)
rowkey = rowkey.decode('utf-8') if isinstance(rowkey, bytes) else rowkey
rowkey = md5_encrypt(rowkey) if rowkey_format == "MD5" else rowkey
column_family_nums = len(table._column_family_names())
for key, value in data.items():
self.__validate_input_type(key, bytes)
self.__validate_input_type(value, bytes)
key_str = key.decode('utf-8')
if column_family_nums > 1 and ":" not in key_str:
raise ValueError("添加的数据没有带上列簇!")
elif column_family_nums == 1 and ":" not in key_str:
column_family_name = table._column_family_names()[0].decode('utf-8')
key = f"{column_family_name}:{key_str}".encode()
tmp_dict[key] = value
data = tmp_dict if tmp_dict else data
try:
table.put(row=rowkey, data=data)
if self.get_rows_datas(rowkeys=[f"{rowkey}"], table_name=table_name):
print(f"rowkey={origin_rowkey_value}的数据更新成功!")
else:
print(f"rowkey={origin_rowkey_value}的数据添加成功!")
return True
except Exception as e:
print(f"数据添加失败!失败原因:{e}")
return False
def upsert_batch_datas(self, datas, table_name=None, rowkey_format=None):
"""
:function: 批量数据插入或批量数据更新
:param datas: 数据集
eg: key可为str或bytes类型 data = {
b'row_key1': {b'cf1:column1': b'value1', b'cf2:column2': b'value2'},
b'row_key2': {b'cf1:column1': b'value3', b'cf2:column2': b'value4'},
# 添加更多的行和列族、列名与值
}
:param table_name: 表名 【初始化连接时指定了表名,可缺省】
:param rowkey_format: rowkey的格式,如果为:None 保持原有格式;如果为:MD5 则先对rowkey进行MD5加密
:return:
"""
if isinstance(datas, dict):
for rowkey, column_data in datas.items():
self.__validate_input_type(column_data, dict)
for key, value in column_data.items():
self.__validate_input_type(key, bytes)
self.__validate_input_type(value, bytes)
self.upsert_one_data(rowkey=rowkey, data=column_data, table_name=table_name, rowkey_format=rowkey_format)
else:
raise ValueError("参数datas的数据类型必须是dict!")
def delete_table_datas(self, rowkeys, columns=None, table_name=None, rowkey_format=None):
"""
:function: 删除表数据(单条/批量)
:param rowkeys: 行号集合 list类型
:param columns: 列名集合 list or tuple类型
:param table_name: 表名
:param rowkey_format: rowkey的格式,如果为:None 保持原有格式;如果为:MD5 则先对rowkey进行MD5加密
:return:
"""
table = self.__get_table_object(table_name)
self.__validate_input_type(rowkeys, list)
if rowkey_format == "MD5":
rowkeys = [md5_encrypt(rowkey) for rowkey in rowkeys]
try:
if columns:
self.__validate_input_type(columns, (list, tuple))
for rowkey in rowkeys:
table.delete(rowkey, columns)
else:
for rowkey in rowkeys:
table.delete(rowkey)
print("删除数据成功!")
return True
except Exception as e:
print(f"删除数据失败, 失败的原因是;{e}")
return False
def delete_by_filter(self, filter, table_name=None):
"""
:function: 根据过滤器删除指定表数据
:param filter: 过滤器表达式 【注:使用列过滤器,表达式最后注意添加 true,true 两个参数,目的是避免误删其他数据】
eg;"SingleColumnValueFilter('f1', 'column1', = , 'binary:value1_006',true, true)"
:param table_name: 表名
:return:
"""
self.__validate_input_type(filter, str)
datas = self.get_table_datas(table_name=table_name, filter=filter)
rowkeys = [data[0] for data in datas]
return self.delete_table_datas(rowkeys=rowkeys)
def close(self):
"""关闭Hbase连接"""
self.conn.close()