第一章 需求分析
- 微博內容的瀏覽,數據庫表設計
- 用戶社交體現:關注用戶,取關用戶
- 拉取關注的人的微博內容
第二章 數據庫設計
設計成三張表微博內容表、用戶關係表和微博收件箱表。
第三章 代碼實現
3.1 創建工程
maven工程依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>HBase</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>guli-weibo</artifactId> <dependencies> <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase-server</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase-client</artifactId> <version>1.3.1</version> </dependency> </dependencies> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> </project>
工程文件夾:
3.2 constants包
在這裏我們定義的是一些常量,例如是命名空間、表明、列名等,以便後面我們需要使用的時候反覆重寫,防止寫錯,也可以進行一個解耦合。
public class Constants { public static Configuration CONFIGURATION = null; // HBase setting 這裏不知道爲什麼要這要寫纔可以,視頻裏面沒有寫這個也可以運行, // 但是我就會報錯,說無法連接,希望有大佬可以解釋一下。 // 那個set裏面第二個放的是你們集羣的名稱,可能會不一樣,看你們自己的命名了。 static { CONFIGURATION = HBaseConfiguration.create(); CONFIGURATION.set("hbase.zookeeper.quorum", "hadoop102,hadoop103,hadoop104"); } // 命名空間 public static final String NAMESPACE = "weibo"; // weibo內容表 public static final String CONTENT_TABLE = "weibo:content"; public static final String CONTENT_TABLE_CF = "info"; public static final int CONTENT_TABLE_VERSIONS = 1; // 用戶關係表 public static final String RELATION_TABLE = "weibo:relation"; // 第一個列族 public static final String RELATION_TABLE_CF1 = "attends"; // 第二個列族 public static final String RELATION_TABLE_CF2 = "fans"; public static final int RELATION_TABLE_VERSIONS = 1; //收件箱表 public static final String INBOX_TABLE = "weibo:inbox"; public static final String INBOX_TABLE_CF = "info"; public static final int INBOX_TABLE_VERSIONS = 2; }
3.3 utils包
這裏主要是進行DDL的操作,例如創建命名空間,以及對錶之類的操作。
public class HBaseUtil { // 1 創建命名空間 public static void createNameSpace(String namespace) throws IOException { } // 2 判斷表是否存在 private static boolean isTableExist(String tableName) throws IOException { } // 3 創建表 public static void createTable(String tableName, int versions, String... cfs) throws IOException { } }
創建命名空間
// 1 創建命名空間 public static void createNameSpace(String namespace) throws IOException { // 1 獲取Connection對象 Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION); // 2 獲取admin對象 Admin admin = connection.getAdmin(); // 3 構建命名空間描述器 NamespaceDescriptor build = NamespaceDescriptor.create(namespace).build(); // 4 創建命名空間 admin.createNamespace(build); // 5 關閉資源 admin.close(); connection.close(); }
判斷表是否存在
// 2 判斷表是否存在 private static boolean isTableExist(String tableName) throws IOException { // 1 獲取Connection對象 Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION); // 2 獲取admin對象 Admin admin = connection.getAdmin(); // 3 判斷是否存在 boolean result = admin.tableExists(TableName.valueOf(tableName)); // 4 關閉資源 admin.close(); connection.close(); // 5 返回結果 return result; }
創建表
// 3 創建表 public static void createTable(String tableName, int versions, String... cfs) throws IOException { // 1 判斷是否傳入了列族信息 if (cfs.length <= 0) { System.out.println("column information is not available."); return ; } // 2 判斷表是否存在 if (isTableExist(tableName)) { System.out.println(tableName + "table is exist."); return; } // 3 獲取Connection對象 Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION); // 4 獲取Admin對象 Admin admin = connection.getAdmin(); // 5 創建表描述器 HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.valueOf(tableName)); // 6 循環添加列族信息 for (String cf : cfs) { HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(cf); // 7 設置版本 hColumnDescriptor.setMaxVersions(versions); hTableDescriptor.addFamily(hColumnDescriptor); } // 8 創建表操作 admin.createTable(hTableDescriptor); // 關閉資源 admin.close(); connection.close(); }
3.4 dao包
這裏是一些DML的操作,例如一些在表中插入數據,或者是刪除數據的操作。
public class HBaseDao { // 1 發佈微博 public static void publishWeiBo(String uid, String content) throws IOException { } // 2 關注用戶 public static void addAttends(String uid, String... attends) throws IOException { } // 3 取關 public static void deleteAttends(String uid, String... dels) throws IOException { } // 4 獲取 public static void getInit(String uid) throws IOException { } // 5 獲取某個人的所有微博詳情 public static void getWeibo(String uid) throws IOException { } }
發微博功能
這一個功能比較難,因爲不僅對於當前發佈微博的人的內容表需要進行更新,並且需要對當前這個人的粉絲的收件表也需要更新,因爲粉絲需要看到關注的人的最新動態。所以這裏需要對兩張表,多個人進行操作。
// 1 發佈微博 public static void publishWeiBo(String uid, String content) throws IOException { // 1 獲取Connection對象 Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION); // 2 get the admin object Admin admin = connection.getAdmin(); // 第一部分:操縱微博內容表 // 1.獲取微博內容表對象 Table conTable = connection.getTable(TableName.valueOf(Constants.CONTENT_TABLE)); // 2. 獲取當前時間戳 long ts = System.currentTimeMillis(); // 3 獲取Rowkey String rowKey = uid + "_" + ts; // 4 創建Put對象 Put conPut = new Put(Bytes.toBytes(rowKey)); // 5 給Put對象賦值 conPut.addColumn(Bytes.toBytes(Constants.CONTENT_TABLE_CF), Bytes.toBytes("content"), Bytes.toBytes(content)); // 6 執行插入數據操作 conTable.put(conPut); // 第二部分:操縱微博收件箱表 // 1 獲取用戶關係表對象 Table relaTable = connection.getTable(TableName.valueOf(Constants.RELATION_TABLE)); // 2 獲取當前發佈微博人的fans列族數據 Get get = new Get(Bytes.toBytes(uid)); // 指定relation關係表的第二個列族 get.addFamily(Bytes.toBytes(Constants.RELATION_TABLE_CF2)); Result result = relaTable.get(get); // 3 創建一個集合,用於存放微博內容表的put對象 ArrayList<Put> inboxPuts = new ArrayList<>(); // 4 遍歷粉絲 for (Cell cell : result.rawCells()) { // 5 構建微博收件箱表的Put對象 Put inboxPut = new Put(CellUtil.cloneQualifier(cell)); // 6 給收件箱表的Put對象賦值 inboxPut.addColumn(Bytes.toBytes(Constants.INBOX_TABLE_CF), Bytes.toBytes(uid), Bytes.toBytes(rowKey)); // 7 將收件箱表的Put對象存入集合 inboxPuts.add(inboxPut); } // 8 判斷是否有粉絲 if (inboxPuts.size() > 0) { // 獲取收件箱表對象 Table inboxTable = connection.getTable(TableName.valueOf(Constants.INBOX_TABLE)); // 執行收件箱表數據插入操作 inboxTable.put(inboxPuts); // 關閉收件箱表 inboxTable.close(); } // 關閉資源 relaTable.close(); conTable.close(); connection.close(); }
關注功能
關注某個人之後,需要獲得這個人的最近發佈的微博,以及在微博關係表當中需要添加新的關係。
// 2 關注用戶 public static void addAttends(String uid, String... attends) throws IOException { // 校驗是否添加了待關注的人 if (attends.length <= 0) { System.out.println("please choose attention person"); return ; } // 獲取Connection對象 Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION); // 第一部分:操作用戶關係表 // 1 獲取用戶關係表對象 Table relaTable = connection.getTable(TableName.valueOf(Constants.RELATION_TABLE)); // 2 創建一個集合,用於存放用戶關係表的Put對象 ArrayList<Put> relaPuts = new ArrayList<>(); // 3 創建操作者的Put對象 Put uidPut = new Put(Bytes.toBytes(uid)); // 4 循環創建被關注者的Put對象 for (String attend : attends) { // 5 給操作者的Put對象賦值 uidPut.addColumn(Bytes.toBytes(Constants.RELATION_TABLE_CF1), Bytes.toBytes(attend), Bytes.toBytes(attend)); // 6 創建被關注者的Put對象 Put attendPut = new Put(Bytes.toBytes(attend)); // 7 給被關注者的Put對象賦值 attendPut.addColumn(Bytes.toBytes(Constants.RELATION_TABLE_CF2), Bytes.toBytes(uid), Bytes.toBytes(uid)); // 8 將被關注者的Put對象放入集合 relaPuts.add(attendPut); } // 9 將操作者的Put對象添加至集合 relaPuts.add(uidPut); // 10 執行用戶關係表的插入數據操作 relaTable.put(relaPuts); // 第二部分:操作收件箱表 // 1 獲取微博內容表對象 Table contTable = connection.getTable(TableName.valueOf(Constants.CONTENT_TABLE)); // 2 創建收件箱表的Put對象 Put inboxPut = new Put(Bytes.toBytes(uid)); // 3 循環attends,獲取每個被關注者的近期發佈的微博 for (String attend : attends) { // 4 獲取當前被關注者的近期發佈的微博(scan)->集合 // 這裏獲取只能用scan,因爲get要指定rowKey,而在關注時是不知道rowKey的時間戳 // 不能全表掃描 這裏使用startRow 和stopRow Scan scan = new Scan(Bytes.toBytes(attend + "_"), Bytes.toBytes(attend + "|")); ResultScanner resultScanner = contTable.getScanner(scan); // define a time stamp long ts = System.currentTimeMillis(); // 5 對獲取的值進行遍歷 // 按rowKey的比較規則時間戳最小的微博最先出來,先放入列中,最後才能拿到最新的 // 這裏其實有問題 如果關注的人發佈太多的微博,只爲了得到最新的三天數據。而去插入大量無用的數據 // 可以在發佈微博函數中反轉時間戳解決 for (Result result : resultScanner) { // 6 給收件箱表的Put對象賦值 inboxPut.addColumn(Bytes.toBytes(Constants.INBOX_TABLE_CF), Bytes.toBytes(attend), ts++, result.getRow()); } } // 7 判斷當前的Put對象是否爲空 if (!inboxPut.isEmpty()) { // 獲取收件箱表對象 Table inboxTable = connection.getTable(TableName.valueOf(Constants.INBOX_TABLE)); // 插入數據 inboxTable.put(inboxPut); // 關閉收件箱表連接 inboxTable.close(); } // 關閉資源 relaTable.close(); contTable.close(); connection.close(); }
取消關注
// 3 取關 public static void deleteAttends(String uid, String... dels) throws IOException { if (dels.length <= 0) { System.out.println("please choose deletion person"); return; } // 獲取Connection對象 Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION); // 第一部分:操作用戶關係表 // 1 獲取用戶關係表對象 Table relatTable = connection.getTable(TableName.valueOf(Constants.RELATION_TABLE)); // 2 創建一個集合,用戶存放用戶關係表的Delete對象 ArrayList<Delete> relatDelete = new ArrayList<>(); // 3 創建操作者的Delete對象 Delete uidDelete = new Delete(Bytes.toBytes(uid)); // 4 循環創建被取關者的Delete對象 for (String del : dels) { // 5 給操作者的Delete對象賦值 uidDelete.addColumns(Bytes.toBytes(Constants.RELATION_TABLE_CF1), Bytes.toBytes(del)); // 6 創建被取關者的Delete對象 Delete delDelte = new Delete(Bytes.toBytes(del)); // 7 給被取關者的Delete對象賦值 delDelte.addColumns(Bytes.toBytes(Constants.RELATION_TABLE_CF2), Bytes.toBytes(uid)); // 8 將被取關者的Delete對象添加至集合 relatDelete.add(delDelte); } // 9 將操作者的Delete對象添加至集合 relatDelete.add(uidDelete); // 10 執行用戶關係表的刪除操作 relatTable.delete(relatDelete);
// 第二部分:操作收件箱表 // 1 獲取收件箱表對象 Table inboxTable = connection.getTable(TableName.valueOf(Constants.INBOX_TABLE)); // 2 創建操作者的Delete對象 Delete inboxDelete = new Delete(Bytes.toBytes(uid)); // 3 給操作者的Delete對象賦值 for (String del : dels) { inboxDelete.addColumns(Bytes.toBytes(Constants.INBOX_TABLE_CF), Bytes.toBytes(del)); } // 4 執行收件箱表的刪除操作 inboxTable.delete(inboxDelete); // 關閉資源 relatTable.close(); inboxTable.close(); connection.close(); }
獲得用戶初始頁
// 4 獲取 public static void getInit(String uid) throws IOException { // 1 獲取Connection對象 Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION); // 2 獲取收件箱表對象 Table inboxTable = connection.getTable(TableName.valueOf(Constants.INBOX_TABLE)); // 3 獲取微博內容表對象 Table conTable = connection.getTable(TableName.valueOf(Constants.CONTENT_TABLE)); // 4 創建收件箱表Get對象,並獲取數據(設置最大版本) Get inboxGet = new Get(Bytes.toBytes(uid)); inboxGet.setMaxVersions(); Result result = inboxTable.get(inboxGet); // 5 遍歷獲取的數據 for (Cell cell : result.rawCells()) { // 6 構建微博內容表Get對象 Get conGet = new Get(CellUtil.cloneValue(cell)); // 7 獲取Get對象的數據內容 Result conResult = conTable.get(conGet); // 8 解析內容並打印 for (Cell conCell : conResult.rawCells()) { System.out.println("RK:" + Bytes.toString(CellUtil.cloneRow(conCell)) + ", CF:" + Bytes.toString(CellUtil.cloneFamily(conCell)) + ", CN:" + Bytes.toString(CellUtil.cloneQualifier(conCell)) + ", Value:" + Bytes.toString(CellUtil.cloneValue(conCell))); } } // 9 關閉資源 inboxTable.close(); conTable.close(); connection.close(); }
獲得用戶全部微博內容
// 5 獲取某個人的所有微博詳情 public static void getWeibo(String uid) throws IOException { // 1 獲取Connection對象 Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION); // 2 獲取微博內容表對象 Table conTable = connection.getTable(TableName.valueOf(Constants.CONTENT_TABLE)); // 構建Scan對象 Scan scan = new Scan(); // 構建過濾器 RowFilter rowFilter = new RowFilter(CompareFilter.CompareOp.EQUAL, new SubstringComparator(uid + "_")); scan.setFilter(rowFilter); // 4 獲取數據 ResultScanner resultScanner = conTable.getScanner(scan); // 5 解析數據並打印 for (Result result : resultScanner) { for (Cell cell : result.rawCells()) { System.out.println("RK:" + Bytes.toString(CellUtil.cloneRow(cell)) + ", CF:" + Bytes.toString(CellUtil.cloneFamily(cell)) + ", CN:" + Bytes.toString(CellUtil.cloneQualifier(cell)) + ", Value:" + Bytes.toString(CellUtil.cloneValue(cell))); } } // 6 關閉資源 conTable.close(); connection.close(); }
3.5 test包 測試
public class TestWeiBo { public static void init() { try { // 創建命名空間 HBaseUtil.createNameSpace(Constants.NAMESPACE); // 創建微博內容表 HBaseUtil.createTable(Constants.CONTENT_TABLE, Constants.CONTENT_TABLE_VERSIONS, Constants.CONTENT_TABLE_CF); // 創建用戶關係表 HBaseUtil.createTable(Constants.RELATION_TABLE, Constants.RELATION_TABLE_VERSIONS, Constants.RELATION_TABLE_CF1, Constants.RELATION_TABLE_CF2); // 創建收件箱表 HBaseUtil.createTable(Constants.INBOX_TABLE, Constants.INBOX_TABLE_VERSIONS, Constants.INBOX_TABLE_CF); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException, InterruptedException { // 初始化 init(); System.out.println("init done"); // 1001 發佈微博 HBaseDao.publishWeiBo("1001","這是1001的微博,Hello World"); System.out.println("publish done"); // 1002 關注 1001, 1003 HBaseDao.addAttends("1002", "1001", "1003"); System.out.println("attend done"); // 獲取1002 初始化頁面 HBaseDao.getInit("1002"); System.out.println("*********************111**********************"); // 1003發佈三條微博 同時1001發佈兩條 HBaseDao.publishWeiBo("1003", "誰說的趕緊下課!!"); Thread.sleep(10); HBaseDao.publishWeiBo("1001", "我沒說話!!"); Thread.sleep(10); HBaseDao.publishWeiBo("1003", "誰說的!!"); Thread.sleep(10); HBaseDao.publishWeiBo("1001", "反正飛機是下線了!!"); Thread.sleep(10); HBaseDao.publishWeiBo("1003", "你們愛咋咋地!!"); // 獲取1002 初始頁面 HBaseDao.getInit("1002"); System.out.println("*********************222***********************"); // 1002 取關 1003 HBaseDao.deleteAttends("1002", "1003"); //獲取1002初始化頁面 HBaseDao.getInit("1002"); System.out.println("*********************333***********************"); // 1002再次關注1003 HBaseDao.addAttends("1002", "1003"); // 獲取1002初始化頁面 HBaseDao.getInit("1002"); System.out.println("*********************444***********************"); // get 1001 detail initial page HBaseDao.getWeibo("1001"); System.out.println("*********************555***********************"); } }