本文是西南交大研究生课程《大数据智慧管理机制》的课程设计,做个笔记马克一下。
课程设计意义与目的
Hbase是非关系型大数据管理技术,基于hadoop的NoSQL,从而具有面向大数据5V特点的存储优势,具有强大的吞吐能力和扩展性。传统的关系型数据库涉及多表联结查询及Group by或Order by等操作,在分布式系统高并发环境下,当数据量达到几千万甚至至几亿级别时,一个SQL查询会达到分钟级别以上,效率低下;而HBase采用的是Key/Value的列式存储方式(数据即索引),即使数据海量,查询效率依然能满足通常的要求。此外,由于基于分布式文件系统,可以将数据分片放在不同的服务器上,进一步增加高并发能力,减少了负载压力。
由于HBase简单的存储方式,它不支持Group by, Order by等操作, 不擅长数据分析,只能借助MapReduce或协处理器( Coprocessor)来实现少量复杂查询。因此,HBase等NoSQL数据库适合千万以上数据量并且需要高并发的应用环境。因此此次的hbase实践有助于我们更深入的学习大数据的处理。
2. 课程设计内容
2.1需求分析及源数据集情况
本设计基于Hbase实现一个股票管理系统,应用NoSQL技术完成大数据的管理和查询任务。应用需求主要有股票记录的数据库维护管理、记录查询、数据统计分析。在记录维护管理模块,设计UI界面,添加、修改和删除数据;在查询和统计分析模块,实现简单的条件查询、组合条件查询、统计分析等任务。
实践项目使用了股票数据集作为源数据,该数据是关于20家上市公司的1991年到2016年的股票信息,数据量约为120000条记录。相关属性如:股票代码,股票简称,日期,前收盘价,开盘价,最高价,最低价,收盘价,成交量,成交金额,涨跌,涨跌幅,均价,换手率,总市值。其中股票代码和股票简称为股票的基本信息,其余为可计算的统计信息。
2.2 逻辑和物理设计,模型展示。
2.2.1系统架构
本设计的后端由数据库Hbase以及它所基于的分布式文件系统HDFS构成,客户端通过web和java服务器与后端通信,Hbase的java api实现数据的增删改查后,将数据返回给前端。
2.2.2 Hbasse数据库模型设计
Hbase与传统的关系型数据库不同,它是一个稀疏的、多维度的、排序的映射表。每张表都有一个列族集合, HBase通过列族的概念来组织数据 的物理存储。每行都有一个可排序的主键(主键之一的行键按字典顺序排列)和任意多个列。行和列所决定的单元中存储数据,数据类型是字节数组byte[]。由于HBase的无模式特性,同一张表里的每一行数据都可以有截然不向的列。HBase中所有数据库在更新时都有一个时间印标记,每一次更新都是一个新的版本,HBase会保留一定数量的版本,客户端可以选择获取距离某个时间点最近的版本单元的值,或者一次获取所有版本单元的值。根据HBase上述特点设计本实践项目中的股票记录表,如表1所示。该表分为两个列族,列族StockInfo包含股票基本信息,列族Statistic包含股票统计信息。
表1 股票记录表
Rowkey | 列族(StackInfo) | 列族(Statistic) | ||
Code | Name | GMV | RANGE | |
03_000001.SZ_20071228 | 000001.SZ | 平安银行 | 2376508379 | 50 |
03_000002.SZ_20071228 | 000002.SZ | 万科A | 601376594.4 | 73.3333 |
…… | ||||
13_000004.SZ_20071228 | 000004.SZ | 中国宝安 | 2832630790 | 11.1484 |
13_000005.SZ_20071228 | 000005.SZ | 深物业A | 2935635546 | 12.7659 |
Hbase中的数据按照rowkey进行排序,region的配置在建表的时候设定。本设计有两个列族,因此一个region有两个store,store以Hfile的形式存储在HDFS上。
2.2.3行键设计
Hbase行键的设计很重要,一条数据的唯一标识就是 rowkey,这条数据存储于哪个分区,取决于 rowkey 处于哪个一个预分区的区间内,设计 rowkey 的主要目的,就是让数据均匀的分布于所有的 region 中,在一定程度上防止数据倾斜。
本设计的rowkey设计方案:将 Rowkey 的高位作为散列字段,由股票的年月进行哈希取余,中位为股票代码,低位放时间字段,例如:
hashvalue (202004) %299 ” + “_” + stock code + “_” + stock date
这样将提高数据均衡分布在每个 Regionserver 实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,将产生所有新数据都在一个 RegionServer 上堆积的热点现象,同时这样在做数据检索的时候负载将会集中在个别 RegionServer,降低查询效率。
最后是表的创建。HBase默认建表时有一个region,这个region的rowkey是没有边界的,即没有startkey和endkey,在数据写入时,所有数据都会写入默认的region,随着数据量的不断增加,此region已经不能承受不断增长的数据量,会进行split,分成2个region。在此过程中,会产生两个问题:
1.数据往一个region上写,会有写热点问题。
2.region split会消耗宝贵的集群I/O资源。
因此为负载均衡,建表的时候,创建多个空region进行预分区,使得不同年月份的股票存在不同regionserver中。由于存储在相同的列族具有相同的特性,因此对于股票数据集,分为两个列族,StackInfo和Statisitc,保存股票基本信息和统计信息。
图2 建表
2.3 系统功能模块和数据分析
2.3.1构建hbase并加载数据
(1)开发环境介绍
环境:Ubuntu21,jdk1.7,hadoop2.6.5,tomcat9.0,hbase1.2.6,开发工具:eclipse。搭建伪分布式环境,使用hbase自带的zookeeper。
(2)使用java api,调用put函数将股票信息导入HbaseStock表中,
插入数据主要代码如下:
Configuration cfg = HBaseConfiguration.create();
//HTable对象用于与HBase进行通信。
HTable table = new HTable(cfg,tableName);
//通过Put对象为已存在的表添加数据
Put put = new Put(row.getBytes());
if(column==null)//判断列限定符是否为空,如果为空,则直接添加列数据
put.add(columnFamily.getBytes(),null,data.getBytes());
else
put.add(columnFamily.getBytes(),column.getBytes(),data.getBytes());
//table对象的put输入参数是put对象,而put对象则表示每一单元格数据。
table.put(put);
(3)在命令行中输入hbase shell进入hbase界面,输入scan ‘HbaseStock’可查看加载进去的数据。
图3 查看所有数据
2.3.2Hbase数据库维护
(1)删除、添加和修改数据
图4 put命令添加数据
图5 delete命令删除数据
Hbase添加和修改都是采用put直接添加数据,修改不会覆盖源数据,只是在数据行上新加了一条时间戳不同的数据。因此在后端处理添加和修改均调用的put函数。关键代码:
public void insert(TableBean tBean) {
try(Connectionconn = this.connect();
TabletableDes =conn.getTable(TableName.valueOf("HbaseStock")) ){
Putput = new Put(Bytes.toBytes(tBean.getRowKey()));
HTableDescriptorhtab = tableDes.getTableDescriptor();
HColumnDescriptor[]columnFamilies = htab.getColumnFamilies();
Stringstatistic = columnFamilies[0].getNameAsString();
StringstockInfo = columnFamilies[1].getNameAsString();
System.out.println(statistic);
System.out.println(stockInfo);
//Code,Name,date,PreClose,OpenPrice,MaxPrice,
//LowPrice,ClosePrice,Vol,TurnVol,UaD,Range,Avg,TurnOver,GMV
put.addColumn(Bytes.toBytes(stockInfo),Bytes.toBytes("Name"),…);
//将修改更新到表
tableDes.put(put);
}catch(Exceptione){
}
}
图6 前端修改删除记录
图7 前端修改添加页面
(2)查询
图8 get命令查询一条数据
查询某一rowkey下的股票信息的关键代码如下:
publicList<TableBean> selectRowKey(String rowKey) {
List<TableBean> tabBeans =new ArrayList<TableBean>();
TableBean tabBean = newTableBean();
try(Connection conn =this.connect();
Table tableDes=conn.getTable(TableName.valueOf("HbaseStock"))){
Get get = newGet(Bytes.toBytes(rowKey));//调用get函数
Result result =tableDes.get(get);
……
tabBean.set…(…)
}catch(Exception e){
System.out.println(e);
return tabBeans;
}
return tabBeans;
}
图9 前端根据行键查询结果
(3)限定范围查询。
限制行键范围输出,使用STARTROW和STOPROW过滤输出,如图9查询同一股票万科A,在2000年11月的全部记录。
图10 命令行限定范围查询
关键代码:
public ArrayList scanRangRow(String tableName,String Start,String End) throws IOException{
Connection connection = this.connect();
Table table = connection.getTable(TableName.valueOf(tableName));
byte[] startRow = Bytes.toBytes(Start);
byte[] endRow = Bytes.toBytes(End);
Scan s = new Scan(startRow,endRow);
ResultScanner rs = table.getScanner(s);
ArrayList L = new ArrayList();
String colFamily = "StockInfo";
String col = "Name";
for(Result result:rs){
System.out.println("--------" + Bytes.toString(result.getRow()));
System.out.println(new String(result.getValue(colFamily.getBytes(),col==null?null:col.getBytes())));
}
table.close();
return L;
}
(4)条件查询
在hbase shell里使用过滤器查询之前先引入相应的包:
import org.apache.hadoop.hbase.filter.CompareFilter
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter
import org.apache.hadoop.hbase.filter.SubstringComparator
列值过滤器查询股票涨跌幅度为0的记录。
Scan 'HbaseStock',{FILTER => "SingleColumnValueFilter('Statistic','Range',=,'binary:0')"}
图11 命令行列值过滤器查询结果
行值过滤法查找股票日期为2007年9月27日的股票信息
scan 'HbaseStock',{FILTER =>"RowFilter(=,'substring:20070927')"}
图12 命令行行值过滤器查询结果
关键代码:
//条件查询
public List<TableBean> select(String name, String ……) throws IOException {
List<Filter> filters = new ArrayList<Filter>();//过滤器列表
List<TableBean> tabBeans = new ArrayList<TableBean>();
nameFilter = new SingleColumnValueFilter(
Bytes.toBytes("StockInfo"), Bytes.toBytes("Code"), CompareOp.EQUAL, new SubstringComparator(name));
filters.add(nameFilter);//添加条件
}
……
filters.add(filter);
}
//创建FilterList
FilterList filterList = new FilterList(Operator.MUST_PASS_ALL, filters);
//FilterList filterList = new FilterList(filters);
Scan scan = new Scan();
scan.setFilter(filterList); //设置过滤器
try(Connection conn = this.connect();
Table tableDes =conn.getTable(TableName.valueOf("HbaseStock"));//lhs_mymoney
ResultScanner rs = tableDes.getScanner(scan);){……}
(5)组合条件查询
(5)组合条件查询
使用AND将多个过滤器连接,进行组合条件查询。例如分析万科A公司在1991年到1992年的股票信息。共10条记录。
scan 'HbaseStock' , {FILTER =>"(SingleColumnValueFilter ('StockInfo','Code',=,'binary:000002.SZ')) AND (RowFilter(=,'substring:1991'))"}
图13 命令行组合查询
关键代码:
//创建FilterList
FilterListfilterList = new FilterList(Operator.MUST_PASS_ALL, filters);//返回满足所有过滤条件的记录
图14 前端组合查询结果
2.3.3数据分析
在对股票市场进行基本了解后发现,股民分析股票的趋势时,通常根据股价和成交量的走势一起分析。比如低位量平价升,即股价从高处滑落时,往往成交量会减少,也就是所谓的缩量,但当一定的缩量之后,成交量如果与前日持平,而且股价已经开始上升,这说明底部已到,可以考虑入手了。同理还有低位量增价平,股价经过持续下跌的低位区,开始出现企稳的迹象,成交量也慢慢增加,可以适量买进股票等待上涨,等等。因此在统计分析模块主要分析成交量与股价的走势对比。由于股价在一天中是动态变化的,因此选择每天的收盘价作为股价。
数据分析部分基于组合条件查询的代码,图16展示了股票代码为000002.SZ公司万科A股1991到1992年股价走势及成交量走势,由两个图对比可知道,在大概1991年9月18号后,股票符合低位量平价升的模式。
图15 股票分析统计查询窗口
图16 股票分析统计
3.遇到的主要问题和解决方案、收获与感悟。
完成课程设计的过程中,因为使用的虚拟机搭建hadoop和hbase环境,虚拟机经常崩溃,以至于很多时间都在重装环境。因此总结以下几个问题:
ssh的安装问题
安装ssh:
sudo apt-get install openssh-server
生成密钥对:
ssh-keygen -t rsa
(一路回车结束,进入到目录.ssh,可以发现生成以下文件:
id_rsa : 生成的私钥文件
id_rsa.pub :生成的公钥文件
如果没有.ssh目录,则执行ssh localhost,将会生成.ssh目录。)
执行以下命令生成授权文件:
cat .ssh/id_rsa.pub >> .ssh/authorized_keys
赋予authorized_keys 文件权限:
chmod 600 .ssh/authorized_keys
验证是否成功:
ssh localhost
启动之后没有Datanode的问题
由于多次格式化namenode,导致再启动会出现没有datanode
解决:首先vi core-site.xml 看有没有配置tmp,如果有,直接到此目录下删除data,如果没有配置,直接找根目录下的tmp目录,然后rm- rf data,再格式化一次hdfs namenode -format,再重启start-dfs.sh (前提是已经stop-dfs.sh)
Eclipse无法启动的问题
Eclipse在安装javaee时出现HostNameError错误。
解决:在hosts文件里添加主机ip地址。实际我是重装了,因为我的ubuntu的apt没有网络的工具,需要apt update,但是在更新apt的时候会一直报错,为了节约时间选择了重装。
Log4j报错
在使用java api连接hbase时,log4j会出现报错,每次执行完出现以下提示:
log4j:WARN No appenders could be found for logger(org.apache.ibatis.logging.LogFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARNSeehttp://logging.apache.org/log4j/1.2/faq.html#noconfigfor more info.
解决:在配置文件log4j.properties(文件名必须这个,放在resources目录)全选粘贴如下代码
# Global logging configuration 开发时候建议使用 debug
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
经过多次重装,终于配置好了!这次课程设计,是我第一次在虚拟环境下编程,由于ubuntu不太稳定,所以经常要等虚拟机恢复响应,设计过程中也考虑过在物理机环境下远程连接hbase,无奈当时虚拟机的终端损坏了,很多配置查找无法进行。但是在多次重装后发现,只要备份好所有配置,搭建这些环境很快的。
图17 hbase配置成功