HBase回顾六、 HBase实战
参考自尚硅谷HBase教程http://www.atguigu.com/
文章目录
需求分析
-
微博内容的浏览,数据库表设计
-
用户社交体现:关注用户,取关用户
-
拉取关注的人的微博内容
代码实现
1 代码设计总览:
-
创建命名空间以及表名的定义
-
创建微博内容表
-
创建用户关系表
-
创建用户微博内容接收邮件表
-
发布微博内容
-
添加关注用户
-
获取用户的初始化页面信息
-
获取用户微博详情
-
测试
2 创建命名空间以及各个表
content表结构:
方法名 | creatTableeContent |
---|---|
Table Name | weibo:content |
RowKey | 用户ID_时间戳 |
ColumnFamily | info |
ColumnLabel | 标题,内容,图片 |
Version | 1个版本 |
relation表结构:
方法名 | createTableRelations |
---|---|
Table Name | weibo:relations |
RowKey | 用户ID |
ColumnFamily | attends、fans |
ColumnLabel | 关注用户ID,粉丝用户ID |
ColumnValue | 用户ID |
Version | 1个版本 |
inbox表结构:
方法名 | createTableReceiveContentEmails |
---|---|
Table Name | weibo:receive_content_email |
RowKey | 用户ID |
ColumnFamily | info |
ColumnLabel | 用户ID |
ColumnValue | 取微博内容的RowKey |
Version | 1000 |
测试代码
package com.chanzany.test;
import com.chanzany.constants.Constants;
import com.chanzany.utils.HBaseUtil;
import java.io.IOException;
public class TestCreateTable {
public static void main(String[] args) throws IOException {
HBaseUtil.createNameSpace(Constants.NAMESPACE);
HBaseUtil.createTable(Constants.CONTENT_TABLE,Constants.CONTENT_TABLE_VERSIONS,Constants.CONTENT_TABLE_FAMILY);
HBaseUtil.createTable(Constants.RELATION_TABLE,Constants.RELATION_TABLE_VERSIONS,Constants.RELATION_TABLE_FAMILY1,Constants.RELATION_TABLE_FAMILY2);
HBaseUtil.createTable(Constants.INBOX_TABLE,Constants.INBOX_TABLE_VERSIONS,Constants.INBOX_TABLE_FAMILY);
}
}
工具类
package com.chanzany.utils;
import com.chanzany.constants.Constants;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import java.io.IOException;
/**
* 操作HBase的工具类
* 1. 创建命名空间
* 2. 判断表是否存在
* 3. 表的创建(三张表)
*/
public class HBaseUtil {
private static ThreadLocal<Connection> connHolder = new ThreadLocal<Connection>();
private static ThreadLocal<Admin> adminHolder = new ThreadLocal<Admin>();
static {
Connection conn = connHolder.get();
Admin admin =adminHolder.get() ;
if (conn == null) {
try {
conn = ConnectionFactory.createConnection(Constants.CONFIGURATION);
connHolder.set(conn);
if (admin == null){
admin=conn.getAdmin();
adminHolder.set(admin);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 创建命名空间
* @param nameSpace
*/
public static void createNameSpace(String nameSpace) throws IOException {
Admin admin = adminHolder.get();
NamespaceDescriptor namespaceDescriptor = NamespaceDescriptor.create(nameSpace).build();
admin.createNamespace(namespaceDescriptor);
}
/**
* 判断表是否存在
* @param tableName
* @return
*/
private static boolean isTableExist(String tableName) throws IOException {
Admin admin = adminHolder.get();
boolean flag = admin.tableExists(TableName.valueOf(tableName));
admin.close();
return flag;
}
public static void createTable(String tableName,int versions,String... columnFamilys) throws IOException {
//判断是否传入了列族信息
if (columnFamilys.length<=0){
System.out.println("Column Family information are not set!");
return;
}
if (isTableExist(tableName)){
System.out.println(tableName+"has already exists!");
return;
}
Admin admin = adminHolder.get();
//设置表的信息,创建表
HTableDescriptor tableDescriptor = new HTableDescriptor(TableName.valueOf(tableName));
for (String cf : columnFamilys) {
//设置每个列族的信息
HColumnDescriptor columnDescriptor = new HColumnDescriptor(cf);
columnDescriptor.setMaxVersions(versions);
//为表描述对象添加列族描述对象
tableDescriptor.addFamily(columnDescriptor);
}
//创建表
admin.createTable(tableDescriptor);
}
public static void close() throws IOException {
Connection conn = connHolder.get();
Admin admin = adminHolder.get();
if (conn != null) {
conn.close();
connHolder.remove();
}
if (admin!=null){
admin.close();
adminHolder.remove();
}
}
}
常量类
package com.chanzany.constants;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
public class Constants {
//HBase配置信息
public static Configuration CONFIGURATION = HBaseConfiguration.create();
//命名空间
public static String NAMESPACE="weibo";
//微博内容表信息
public static String CONTENT_TABLE="weibo:content";
public static String CONTENT_TABLE_FAMILY="info";
public static int CONTENT_TABLE_VERSIONS= 1;
//用户关系表信息
public static String RELATION_TABLE="weibo:relation";
public static String RELATION_TABLE_FAMILY1="attends";
public static String RELATION_TABLE_FAMILY2="fans";
public static int RELATION_TABLE_VERSIONS= 1;
//收件箱表
public static String INBOX_TABLE="weibo:inbox";
public static String INBOX_TABLE_FAMILY="info";
public static int INBOX_TABLE_VERSIONS= 2;
}
3 发布微博内容
需要添加数据的表:
a、微博内容表中添加1条数据
b、微博收件箱表对所有粉·数据
示意图:
代码实现方法
public static void publishBlog(String uid,String content) throws IOException {
//操作微博内容表
Connection conn = ConnectionFactory.createConnection(Constants.CONFIGURATION);
Table contentTable = conn.getTable(TableName.valueOf(Constants.CONTENT_TABLE));
//rowKey
long ts = System.currentTimeMillis();
String rowKey = uid+"_"+ts;
//Put对象
Put contentPut = new Put(Bytes.toBytes(rowKey));
contentPut.addColumn(Bytes.toBytes(Constants.CONTENT_TABLE_FAMILY),
Bytes.toBytes("content"),
Bytes.toBytes(content));
contentTable.put(contentPut);
//操作关联表并修改微博收件箱表
// 先获取用户关系表,从关系表的fans列族中找到所有列(粉丝)
Table relationTable = conn.getTable(TableName.valueOf(Constants.RELATION_TABLE));
Get get = new Get(Bytes.toBytes(uid));
get.addFamily(Bytes.toBytes(Constants.RELATION_TABLE_FAMILY2));
Result result = relationTable.get(get);
// 构建一个容器,存放需要放入IndexBox表的Put对象
ArrayList<Put> inBoxPuts = new ArrayList<Put>();
for (Cell cell : result.rawCells()) {
Put inBoxPut = new Put(CellUtil.cloneQualifier(cell));
//列族-列-内容
inBoxPut.addColumn(Bytes.toBytes(Constants.INBOX_TABLE_FAMILY),
Bytes.toBytes(uid),
Bytes.toBytes(rowKey));
inBoxPuts.add(inBoxPut);
}
//判断是否有粉丝
if (inBoxPuts.size()>0){
//获取收件箱表,将inBoxPuts的数据插入其中
Table inboxTable = conn.getTable(TableName.valueOf(Constants.INBOX_TABLE));
inboxTable.put(inBoxPuts);
inboxTable.close();
}
//释放资源
relationTable.close();
contentTable.close();
conn.close();
}
测试结果
@Test
public void testPublish() throws IOException {
HBaseDao.publishBlog("1001","no time to waste!");
}
4 添加关注用户
a、在微博用户关系表中,对当前主动操作的用户添加新关注的好友
b、在微博用户关系表中,对被关注的用户添加新的粉丝
c、微博收件箱表中添加所关注的用户发布的微博
示意图:
代码实现
/**
* 关注用户逻辑
* a、在微博用户关系表中,对当前主动操作的用户添加新的关注的好友
* b、在微博用户关系表中,对被关注的用户添加粉丝(当前操作的用户)
* c、当前操作用户的微博收件箱添加所关注的用户发布的微博rowkey
*/
public static void addAttends(String uid,String... attends ) throws IOException {
if (attends.length<=0){
System.out.println("no attends were checked!");
return;
}
//操作relation表
Connection conn = ConnectionFactory.createConnection(Constants.CONFIGURATION);
Table relationTable = conn.getTable(TableName.valueOf(Constants.RELATION_TABLE));
//创建集合存放所有需要添加的列对应的Put对象
ArrayList<Put> relationPuts = new ArrayList<Put>();
//创建操作者的Put对象
Put userPut = new Put(Bytes.toBytes(uid));
//遍历关注者为userPut赋值并创建attendPut对象
for (String attend : attends) {
//为userPut赋值
userPut.addColumn(Bytes.toBytes(Constants.RELATION_TABLE_FAMILY1),
Bytes.toBytes(attend),
Bytes.toBytes(attend));
//为对应多个列的各个attendPut对象赋值并添加到列表
Put attendPut = new Put(Bytes.toBytes(attend));
attendPut.addColumn(Bytes.toBytes(Constants.RELATION_TABLE_FAMILY2),
Bytes.toBytes(uid),
Bytes.toBytes(uid));
relationPuts.add(attendPut);
}
relationPuts.add(userPut);
relationTable.put(relationPuts);
//操作inbox表
//获取content内容表对象
Table contentTable = conn.getTable(TableName.valueOf(Constants.CONTENT_TABLE));
//创建需要放入收件箱表的Put对象
Put inboxPut = new Put(Bytes.toBytes(uid));
//循环attends,获取每个被关注者的近期发布微博
for (String attend : attends) {
//获取当前被关注对象的近期发布微博,由于rowKey带时间戳是未知的。所有要用scan{startRow,stopRow}来获取
Scan scan = new Scan();
scan.setStartRow(Bytes.toBytes(attend+"_"));
scan.setStopRow(Bytes.toBytes(attend+"|"));
ResultScanner resultScanner = contentTable.getScanner(scan);
//定义时间戳为保证当前被关注对象所发布的微博时间戳不同
long ts = System.currentTimeMillis();
//给需要放入收件箱表的Put对象赋值
for (Result result : resultScanner) {
//content表里的rowKey作为inbox的value,被关注对象attends作为Inbox的多个column
inboxPut.addColumn(Bytes.toBytes(Constants.INBOX_TABLE_FAMILY),Bytes.toBytes(attend),ts++,result.getRow());
}
resultScanner.close();
}
//把inboxPut对象放入收件箱表
if (!inboxPut.isEmpty()){
Table inboxTable = conn.getTable(TableName.valueOf(Constants.INBOX_TABLE));
inboxTable.put(inboxPut);
inboxTable.close();
}
relationTable.close();
contentTable.close();
conn.close();
}
测试结果
@Test
public void testPublish() throws IOException {
HBaseDao.publishBlog("1001","no time to waste!");
HBaseDao.publishBlog("1001","no time to waste1!");
HBaseDao.publishBlog("1001","no time to waste2!");
HBaseDao.publishBlog("1001","no time to waste3!");
HBaseDao.publishBlog("1002","no time to waste!");
HBaseDao.publishBlog("1003","no time to waste!");
HBaseDao.publishBlog("1003","no time to waste1!");
HBaseDao.publishBlog("1003","no time to waste2!");
}
@Test
public void testAddAttends()throws Exception{
HBaseDao.addAttends("1001","1002","1003");
}
5 移除(取关)用户
a、在微博用户关系表中,对当前主动操作的用户移除取关的好友(attends)
b、在微博用户关系表中,对被取关的用户移除粉丝
c、微博收件箱中删除取关的用户发布的微博
示意图
代码实现
/**
* 取消关注(remove)
* a、在微博用户关系表中,对当前主动操作的用户删除对应取关的好友
* b、在微博用户关系表中,对被取消关注的人删除粉丝(当前操作人)
* c、从收件箱中,删除取关的人对应的列
*/
public static void removeAttends(String uid, String... delAttends) throws IOException {
Connection conn = ConnectionFactory.createConnection(Constants.CONFIGURATION);
//操作关系表
Table relationTable = conn.getTable(TableName.valueOf(Constants.RELATION_TABLE));
//操作者需要通过Delete对象来删除attend列族下取关的对象列
Delete userDelete = new Delete(Bytes.toBytes(uid));
ArrayList<Delete> deleteList = new ArrayList<Delete>();
for (String delAttend : delAttends) {
//需要拿到每个被取关的对象,每个被取关的对象删除fans列族下的当前用户列
Delete deleted = new Delete(Bytes.toBytes(delAttend));
deleted.addColumns(Bytes.toBytes(Constants.RELATION_TABLE_FAMILY2),Bytes.toBytes(uid));
deleteList.add(deleted);
//为操作者的delete对象赋值-各个需要取关的对象uid列
userDelete.addColumns(Bytes.toBytes(Constants.RELATION_TABLE_FAMILY1),Bytes.toBytes(delAttend));
}
deleteList.add(userDelete);
relationTable.delete(deleteList);
//操作收件箱表
Table inboxTable = conn.getTable(TableName.valueOf(Constants.INBOX_TABLE));
//将当前用户(rowKey)需要取关的用户对应的列删除
Delete inboxDelete = new Delete(Bytes.toBytes(uid));
for (String delAttend : delAttends) {
inboxDelete.addColumn(Bytes.toBytes(Constants.INBOX_TABLE_FAMILY),Bytes.toBytes(delAttend));
}
inboxTable.delete(inboxDelete);
//释放资源
relationTable.close();
inboxTable.close();
conn.close();
}
测试
运行测试代码
@Test
public void testRemoveAttends()throws Exception{
HBaseDao.removeAttends("1001","1002","1003");
}
测试结果符合期待,成功。
6 获取用户的初始化页面信息
a. 从Inbox表中拿到所关注的所有好友的列下的value(作为content表的rowKey)
b. 根据获取的RowKey,得到微博的内容
示意图:
代码实现
/**
* 获取某个人的初始化页面数据
* @param uid
*/
public static void getInitInfo(String uid) throws IOException {
Connection conn = ConnectionFactory.createConnection(Constants.CONFIGURATION);
//操作收件箱表
Table inboxTable = conn.getTable(TableName.valueOf(Constants.INBOX_TABLE));
//创建收件箱表Get对象,获取uid(rowKey)对应的数据(设置最大版本)
Get userGet = new Get(Bytes.toBytes(uid));
userGet.addFamily(Bytes.toBytes(Constants.INBOX_TABLE_FAMILY));
userGet.setMaxVersions();
Result result = inboxTable.get(userGet);
//创建一个存放contentId的集合
ArrayList<String> contentIds = new ArrayList<String>();
for (Cell cell : result.rawCells()) {
String contentId= Bytes.toString(CellUtil.cloneValue(cell));
contentIds.add(contentId);
}
//操作微博内容表
Table contentTable = conn.getTable(TableName.valueOf(Constants.CONTENT_TABLE));
ArrayList<Get> contentGets = new ArrayList<Get>();
for (String contentId : contentIds) {
Get contentGet = new Get(Bytes.toBytes(contentId));
contentGet.addFamily(Bytes.toBytes(Constants.CONTENT_TABLE_FAMILY));
contentGets.add(contentGet);
}
Result[] detailResults = contentTable.get(contentGets);
//解析内容并打印
for (Result detailResult : detailResults) {
for (Cell cell : detailResult.rawCells()) {
System.out.print("rowKey:"+Bytes.toString(CellUtil.cloneRow(cell))+"\t");
System.out.print("columnFamily:"+Bytes.toString(CellUtil.cloneFamily(cell))+"\t");
System.out.print("column:"+Bytes.toString(CellUtil.cloneQualifier(cell))+"\t");
System.out.print("value:"+Bytes.toString(CellUtil.cloneValue(cell)));
}
System.out.println();
}
//释放资源
contentTable.close();
inboxTable.close();
conn.close();
}
测试
执行测试代码
@Test
public void testGetInitInfo()throws Exception{
HBaseDao.getInitInfo();
}
因为在Inbox表设计的时候指定的是两个版本,所以只会显示最新发布的两条微博内容,符合我们的期待,测试成功。
7 获取用户的所有微博
a、设计过滤器,将传入的uid在content表中对应的微博rowKey拿到
b、遍历拿到的所有微博id,然后进行消费,在真实生成环境中,往往把这些查询结果返回给提交申请的用户。
示意图:
代码实现
/**
* 获取某个人的所有微博详情
*
* @param uid
* @throws Exception
*/
public static List getBlogs(String uid) throws Exception {
//只需要操作content表
Connection conn = ConnectionFactory.createConnection(Constants.CONFIGURATION);
Table contentTable = conn.getTable(TableName.valueOf(Constants.CONTENT_TABLE));
Scan scan = new Scan();
//content表的RowKey是以uid_ts的形式生成的。所以要根据传入的uid找到其对应的所有rowKey
RowFilter rowFilter = new RowFilter(CompareFilter.CompareOp.EQUAL, new SubstringComparator(uid));
scan.setFilter(rowFilter);
ResultScanner resultScanner = contentTable.getScanner(scan);
ArrayList<Cell> cellResults = new ArrayList<Cell>();
for (Result result : resultScanner) {
cellResults.addAll(Arrays.asList(result.rawCells()));
}
contentTable.close();
conn.close();
return cellResults;
}
测试
@Test
public void testGetBlogs() throws Exception{
List blogs = HBaseDao.getBlogs("1003");
for (Object cell : blogs) {
System.out.print("rowKey:" + Bytes.toString(CellUtil.cloneRow((Cell) cell)) + "\t");
System.out.print("columnFamily:" + Bytes.toString(CellUtil.cloneFamily((Cell) cell)) + "\t");
System.out.print("column:" + Bytes.toString(CellUtil.cloneQualifier((Cell) cell)) + "\t");
System.out.println("value:" + Bytes.toString(CellUtil.cloneValue((Cell) cell)));
}
}
测试结果:
满足预期,测试成功
至此,所有需求已实现,代码已上传https://download.csdn.net/download/qq_41819729/12551598