基於hbase 的微博案例

需求

1、 發佈微博內容    

   a. 在微博內容表中 添加一條數據(發佈者)
   b. 在微博內容接收郵件箱表對所有粉絲用戶添加數據(訂閱者)
       scan 'weibo:receive-content-email',{VERSIONS=>5}

2、添加關注用戶

   a. 在微博用戶關係表中 添加新的好友關注(attends)

   b. 從被關注用戶角度來說, 新增粉絲用戶(fans)
   
   c. 微博郵件箱表添加關注用戶發佈的微博內容

3、移除或者取消關注用戶

   a. 在微博用戶關係表中 移除新的好友關注(attends)
   b. 從被關注用戶角度來說, 刪除粉絲用戶(fans)
   c. 微博郵件箱表刪除關注用戶發佈的微博內容 

 

4、獲取關注用戶發佈的微博內容

  a. 從微博內容郵件表中獲取該用戶其關注用戶的微博內容的rowkey
  b. 根據上面獲取到的微博內容的rowkey 獲取微博內容
  微博展示的內容信息:
  message: 發佈者ID , 時間戳 , content

表的設計

分析微博:
   用戶羣體: 關注用戶 和用戶粉絲
   用戶行爲: 發佈微博、添加或者移除關注用戶
   數據存儲: 分佈式 mysql 數據庫
              考慮因素:響應時間 妙級 無延遲
              對你的技術團隊就是一個很大的考驗

              引出hbase 數據庫存儲  來實現響應時間 妙級 無延遲 、
              hbase 是hadoop 的分佈式數據庫


 使用數據庫的時候  
   攔路虎: 表的設計(合理的來設計 因素多元化)
 

   命名空間(類似於傳統關係型數據庫中的schema): 區分不同的業務表
    namespace name:weibo
   設計那些表:


    a.微博內容表

          xxx 發佈 xx 內容
          table name : weibo:content
          
          rowkey: 被關注用戶ID_時間戳
          
          columnfamily: cf
          
          colunmnlabel: (任意變化的)
                       圖片
                       內容
                       標題
          
          version:    只需要一個版本


     rowkey:
         a.唯一性  每條數據是唯一的
         b.長度 (<=64 kb 建議 10-100 byte 最佳 8-16 byte)表 rowkey 是hbase中數據產生冗餘的因素
         c.散列原則
            舉例:
               時間戳_用戶ID 作爲rowkey 
               大量的用戶在同一時刻 發佈微博內容
               121_001
               121_002
               121_003
               121_004
               ===> 
               集中到某個region 會造成單獨幾個region 負載量偏大 而其他 region 完全沒有負載

          d. 業務相關的設計規範:
                方便查詢 儘可能將查詢知道放到 rowkey
     列簇設計:
         Hbase 是面向列簇存儲 region start  rowkey 到 stop rowkey 範圍內的一個列簇下的數據 對應一個hdfs file  稱爲StoreFile 也可以稱爲HFile 所以如果跨列查詢 速度相對來說就會慢很多 so 設計hbase 表 列簇的是 一般1-2個,(1個最佳)

    b.用戶關係表

         用戶id fans attends
         table name : weibo:relations
          
          rowkey: 用戶ID(發佈者的用戶ID)
          
          columnfamily: attends、fans
          
          colunmnlabel: 
                       關注用戶ID
                       粉絲用戶ID
          colunmnvalue: 關注用戶ID
                        粉絲用戶ID
          
          version:    只需要一個版本


    c.用戶微博內容接收郵件箱表

          table name : weibo:receive-content-email
          
          rowkey: 用戶ID(粉絲用戶ID)
          
          columnfamily: cf
          
          colunmnlabel: 
                       用戶ID(發佈者ID 被關注用戶ID) 
          colunmnvalue:

                        取微博內容的rowkey
          
          version:     1000
          10001: cf_001_yyyyy
          10001: cf_001_xxxxx

hbase 常用命令:

          1. disable 'weibo:content': 禁用表
          2. drop 'weibo:content': 刪除表

          3.truncate  'weibo:relations' :清空表數據
          - Disabling table...
          - Dropping table...
          - Creating table...
          list_namespace: 查看命名空間
          list: 查看錶的列表信息

          default:  默認使用的命名空間                                                                                                    
          hbase : 系統默認使用命名空間
          drop_namespace 'weibo': 刪除指定的命名空間

代碼實現

package com.ibeifeng.hbase.weibo.hbase_weibo;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.MasterNotRunningException;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.ZooKeeperConnectionException;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HConnection;
import org.apache.hadoop.hbase.client.HConnectionManager;
import org.apache.hadoop.hbase.client.HTableInterface;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
import org.apache.hadoop.hbase.filter.RowFilter;
import org.apache.hadoop.hbase.filter.SubstringComparator;
import org.apache.hadoop.hbase.util.Bytes;

public class WeiBo {
	private Configuration conf =HBaseConfiguration.create();
	private static final  byte[] CONTENT=Bytes.toBytes("weibo:content");
	private static final  byte[] RELATIONS=Bytes.toBytes("weibo:relations");
	private static final  byte[] RECEIVE_CONTENT_EMAIL=Bytes.toBytes("weibo:receive-content-email");

	//創建 命名空間(庫)
	public void initNameSpace(){
		HBaseAdmin  admin=null;

		try {
			admin=new HBaseAdmin(conf);
			/**
			 * 命名空間(類似於傳統關係型數據庫中的schema): 區分不同的業務表
    			namespace name:weibo
			 */
			NamespaceDescriptor weibo=NamespaceDescriptor.create("weibo")
					.addConfiguration("creator", "beifeng")//
					.addConfiguration("createTime", System.currentTimeMillis()+"")
					.build();
			admin.createNamespace(weibo);

		} catch (MasterNotRunningException e) {
			e.printStackTrace();
		} catch (ZooKeeperConnectionException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(null!=admin){
				try {
					admin.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	// 建表
	public void initTable(){
		createContent();
		createRelations();
		createReceiveContentEmails();
	}
	private void createContent() {
		/**
		 * a.微博內容表:  xxx 發佈 xx 內容
    	  table name : weibo:content

    	  rowkey: 用戶ID_時間戳

          columnfamily: cf

          colunmnlabel: 
	                       圖片
	                       內容
	                       標題

	      version:	只需要一個版本
		 */
		HBaseAdmin admin=null;
		try {
			admin=new HBaseAdmin(conf);
			HTableDescriptor content=new HTableDescriptor(TableName.valueOf(CONTENT));
			HColumnDescriptor c_cf=new HColumnDescriptor(Bytes.toBytes("cf"));
			c_cf.setBlockCacheEnabled(true);
			//推薦是計算後的值
			c_cf.setBlocksize(2097152);
			// 一定事先配置好
			//			c_cf.setCompressionType(Algorithm.SNAPPY);
			c_cf.setMaxVersions(1);
			c_cf.setMinVersions(1);

			content.addFamily(c_cf);
			admin.createTable(content);

		} catch (MasterNotRunningException e) {
			e.printStackTrace();
		} catch (ZooKeeperConnectionException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(null!=admin){
				try {
					admin.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}


	private void createRelations() {
		/**
		 * b.用戶關係表:
		              用戶id fans attends
		    	 table name : weibo:relations

		    	  rowkey: 用戶ID

		          columnfamily: attends、fans

		          colunmnlabel: 
		                       關注用戶ID
		                       粉絲用戶ID
		       colunmnvalue: 用戶ID

		       version:	只需要一個版本
		 */
		HBaseAdmin admin=null;
		try {
			admin=new HBaseAdmin(conf);
			HTableDescriptor relations=new HTableDescriptor(TableName.valueOf(RELATIONS));

			HColumnDescriptor attends=new HColumnDescriptor(Bytes.toBytes("attends"));
			attends.setBlockCacheEnabled(true);
			attends.setBlocksize(2097152);

			attends.setMaxVersions(1);
			attends.setMinVersions(1);

			HColumnDescriptor fans=new HColumnDescriptor(Bytes.toBytes("fans"));
			fans.setBlockCacheEnabled(true);
			fans.setBlocksize(2097152);

			fans.setMaxVersions(1);
			fans.setMinVersions(1);

			relations.addFamily(attends);
			relations.addFamily(fans);

			admin.createTable(relations);
		} catch (MasterNotRunningException e) {
			e.printStackTrace();
		} catch (ZooKeeperConnectionException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(null!=admin){
				try {
					admin.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	private void createReceiveContentEmails() {
		/**
		 * c.用戶微博內容接收郵件箱表:
	    	  table name : weibo:receive-content-email

	    	  rowkey: 用戶ID

	          columnfamily: cf

	          colunmnlabel: 
	                       	用戶ID 
	          colunmnvalue:

	                                                                        取微博內容的rowkey
	           version: 	1000
		 */
		HBaseAdmin admin=null;
		try {
			admin=new HBaseAdmin(conf);
			HTableDescriptor receive_content_email=new HTableDescriptor(TableName.valueOf(RECEIVE_CONTENT_EMAIL));

			HColumnDescriptor rce_cf =new HColumnDescriptor(Bytes.toBytes("cf"));

			rce_cf.setBlockCacheEnabled(true);
			rce_cf.setBlocksize(2097152);

			rce_cf.setMaxVersions(1000);
			rce_cf.setMinVersions(1000);

			receive_content_email.addFamily(rce_cf);

			admin.createTable(receive_content_email);

		} catch (MasterNotRunningException e) {
			e.printStackTrace();
		} catch (ZooKeeperConnectionException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(null!=admin){
				try {
					admin.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	/**
	 * 發佈微博內容:
	 *  a. 在微博內容表中 添加一條數據(發佈者)
	    b. 在微博內容接收郵件箱表對所有粉絲用戶添加數據(訂閱者)
	 * @param uid 
	 *          發佈者ID
	 * @param content
	 *          發佈微博內容
	 */
	public void publishContent(String uid,String content){
		HConnection connection=null;
		try {
			connection=HConnectionManager.createConnection(conf);
			//a. 在微博內容表中 添加一條數據(發佈者)
			HTableInterface contentTBL=connection.getTable(TableName.valueOf(CONTENT));
			long timestamp=System.currentTimeMillis();
			String rowkey=uid+"_"+timestamp;
			Put put = new Put(Bytes.toBytes(rowkey));

			//四個值分別代表colfamily,columnlabel,timestamp,value
			put.add(Bytes.toBytes("cf"), Bytes.toBytes("content"),timestamp, Bytes.toBytes(content));

			contentTBL.put(put);

			//b. 在微博內容接收郵件箱表對所有粉絲用戶添加數據(訂閱者)
			// 1. 查詢 用戶關係表 獲取該用戶的粉絲用戶 
			HTableInterface relationsTBL=connection.getTable(TableName.valueOf(RELATIONS));

			// get 'tablename','rowkey','cf','cq'

			
			//rowkey
			Get get=new Get(Bytes.toBytes(uid));

			//cf
			get.addFamily(Bytes.toBytes("fans"));

			Result result = relationsTBL.get(get);

			List<byte[]> fans = new ArrayList<byte[]>();
			
			//設置uid用戶下所有的粉絲
			for (Cell cell : result.rawCells()) {
				//RELATIONS表裏的columnlabel成爲RECEIVE_CONTENT_EMAIL的rowkey
				fans.add(CellUtil.cloneQualifier(cell));
			}
			// 數據判斷
			if(fans.size() <= 0) return ;
			// 獲取微博內容郵件箱表
			HTableInterface rceTBL=connection.getTable(RECEIVE_CONTENT_EMAIL);
			List<Put> puts=new ArrayList<Put>();
 
			for (byte[] fan : fans) {
				Put fanPut=new Put(fan); //設置rowkey
				fanPut.add(Bytes.toBytes("cf"), Bytes.toBytes(uid),timestamp, Bytes.toBytes(rowkey));
				puts.add(fanPut);
			}
			rceTBL.put(puts);
			/**
			 *  cell :
			    primary key					
			   {rowkey       ,   column(family+label),version(timestamp)}:value
			 */
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(null!=connection){
				try {
					connection.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}
	/**
	 * 添加關注用戶:
	 *   a. 在微博用戶關係表中 添加新的好友關注(attends)

   		 b. 從被關注用戶角度來說, 新增粉絲用戶(fans)

   		 c. 微博郵件箱表添加關注用戶發佈的微博內容
	 * @param uid
	 *           粉絲 ID
	 * @param attends
	 * 			 被關注用戶ID	
	 */
	public void addAttends(String uid, String ...attends){

		//參數過濾
		if(attends==null||attends.length<=0) return;
		/**
		 * a.在微博用戶關係表中 添加新的好友關注(attends)
		 * (0001) ,(0002,0003)
		 * rowkey   column      value
		 * 0001    attends:0002,0002
		 * 0001    attends:0003,0003
		 * 
		 * rowkey  column    value
		 * 0003    fans:0001,0001
		 * 0002    fans:0001,0001
		 * 
		 */
		HConnection connection=null;
		try {
			connection=HConnectionManager.createConnection(conf);

			HTableInterface realtionsTBL=connection.getTable(RELATIONS);
			List<Put> puts=new ArrayList<Put>();
			//a. 在微博用戶關係表中 添加新的好友關注(attends)
			Put attendsPut=new Put(Bytes.toBytes(uid));
			for (String attend : attends) {
				attendsPut.add(Bytes.toBytes("attends"), Bytes.toBytes(attend), Bytes.toBytes(attend));
				//b. 從被關注用戶角度來說, 新增粉絲用戶(fans)
				Put fansPut=new Put(Bytes.toBytes(attend));
				fansPut.add(Bytes.toBytes("fans"), Bytes.toBytes(uid), Bytes.toBytes(uid));
				puts.add(fansPut);
			}
			puts.add(attendsPut);
			realtionsTBL.put(puts);

			//c. 微博郵件箱表添加關注用戶發佈的微博內容的rowkey
			/**
			 * 1. 首先查詢被關注用戶ID發佈的微博內容的rowkey
			 *     單個被關注用戶ID,  --查詢content ->微博內容的rowkey
			 *     0001_xxx
			 *     0001_aaa
			 *     0002_yyy
			 *     0002_zzz
			 * 2. 將前面獲取的rowkey列表 遍歷出來在微博內容郵件表中正式添加數據
			 * 
			 */
			HTableInterface contentTBL= connection.getTable(CONTENT);
			Scan scan =new Scan();
			List<byte[]> rowkeys=new ArrayList<byte[]>();
			for(String attend: attends){
				//掃描表的rowkey,含有字符串("被關注用戶ID_")
				RowFilter filter=new RowFilter(CompareOp.EQUAL, new SubstringComparator(attend+"_"));
				scan.setFilter(filter);
				ResultScanner result=contentTBL.getScanner(scan);
				// 迭代器遍歷
				Iterator<Result> itearor=result.iterator();
				while(itearor.hasNext()){
					Result r=itearor.next();
					for(Cell cell:r.rawCells()){
						rowkeys.add(CellUtil.cloneRow(cell));
					}
				}
			}
			if(rowkeys.size()<= 0) return;
			// 2. 將前面獲取的rowkey列表 遍歷出來在微博內容郵件表中正式添加數據
			HTableInterface rceTBL=connection.getTable(RECEIVE_CONTENT_EMAIL);
			List<Put> rcePuts=new ArrayList<Put>();
			for (byte[] rk : rowkeys) {
				Put put =new Put(Bytes.toBytes(uid));
				String rowkey=Bytes.toString(rk);
				// substring 包前不包後
				String attend=rowkey.substring(0, rowkey.indexOf("_"));
				long timestamp=Long.parseLong(rowkey.substring(rowkey.indexOf("_")+1));
				put.add(Bytes.toBytes("cf"), Bytes.toBytes(attend),timestamp, rk);
				rcePuts.add(put);
			}	
			rceTBL.put(rcePuts);

		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(null!=connection){
				try {
					connection.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}
	/**
	 *移除或者取消關注用戶
	   a. 在微博用戶關係表中 移除新的好友關注(attends)
	   b. 從被關注用戶角度來說, 刪除粉絲用戶(fans)
	   c. 微博郵件箱表刪除關注用戶發佈的微博內容
	 * @param uid
	 * 			粉絲用戶ID
	 * @param attends
	 *          被關注用戶ID
	 */
	public void removeAttends(String uid,String ...attends){
		//參數過濾
		if(attends==null||attends.length<=0) return;
		HConnection connection=null;
		try {
			connection=HConnectionManager.createConnection(conf);
			//a. 在微博用戶關係表中 移除新的好友關注(attends)
			HTableInterface relationsTBL=connection.getTable(RELATIONS);
			List<Delete> deletes=new ArrayList<Delete>();
			Delete attendsDelete =new Delete(Bytes.toBytes(uid));

			for (String attend : attends) {
				attendsDelete.deleteColumn(Bytes.toBytes("attends"), Bytes.toBytes(attend));
				//b. 從被關注用戶角度來說, 刪除粉絲用戶(fans)
				Delete fansDelete=new Delete(Bytes.toBytes(attend));
				fansDelete.deleteColumn(Bytes.toBytes("fans"), Bytes.toBytes(uid));
				deletes.add(fansDelete);
			}

			deletes.add(attendsDelete);
			relationsTBL.delete(deletes);

			//c. 微博郵件箱表刪除關注用戶發佈的微博內容
			HTableInterface rceTBL=connection.getTable(RECEIVE_CONTENT_EMAIL);
			Delete rceDelete=new Delete(Bytes.toBytes(uid));
			for(String attend:attends){
				/**
				 * Delete the latest version of the specified column.
				 */
				// rceDelete.deleteColumn(Bytes.toBytes("cf"), Bytes.toBytes(attend));
				// Delete all versions of the specified column with a timestamp less than
				rceDelete.deleteColumns(Bytes.toBytes("cf"), Bytes.toBytes(attend), System.currentTimeMillis()+Integer.MAX_VALUE);

			}
			rceTBL.delete(rceDelete);

		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(null!=connection){
				try {
					connection.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}
	/**
	 * 粉絲用戶 去獲取關注用戶發佈的微博內容:
	 *  a. 從微博內容郵件表中獲取該用戶其關注用戶的微博內容的rowkey
  		b. 根據上面獲取到的微博內容的rowkey 獲取微博內容
	 * @param uid
	 * 		  粉絲用戶ID
	 */
	public List<Message> getAttendsContens(String uid){
		HConnection connection=null;
		List<Message> messages=new ArrayList<Message>();
		try {
			connection=HConnectionManager.createConnection(conf);
			//a. 從微博內容郵件表中獲取該用戶其關注用戶的微博內容的rowkey
			HTableInterface rceTBL=connection.getTable(RECEIVE_CONTENT_EMAIL);
			Get get=new Get(Bytes.toBytes(uid));
			get.setMaxVersions(5);
			List<byte[]> rowkeys=new ArrayList<byte[]>();
			Result result=rceTBL.get(get);
			for(Cell cell:result.rawCells()){
				
				//CellUtil.cloneValue      獲取value
				//CellUtil.cloneRow        獲取rowkey
				//CellUtil.cloneQualifier  獲取列名
				//CellUtil.cloneFamily     獲取到列族名
				rowkeys.add(CellUtil.cloneValue(cell));
			}
			//b. 根據上面獲取到的微博內容的rowkey 獲取微博內容
			HTableInterface contentTBL =connection.getTable(CONTENT);
			List<Get> gets=new ArrayList<Get>();
			for (byte[] rk : rowkeys) {
				Get g=new Get(rk);
				gets.add(g);
			}
			Result[] results=contentTBL.get(gets);
			for (Result res : results) {
				for(Cell cell:res.rawCells()){
					Message message=new Message();
					String rowkey=Bytes.toString(CellUtil.cloneRow(cell));
					String userid=rowkey.substring(0, rowkey.indexOf("_"));
					String timestamp=rowkey.substring(rowkey.indexOf("_")+1);
					String content=Bytes.toString(CellUtil.cloneValue(cell));
					message.setUid(userid);
					message.setTimestamp(timestamp);
					message.setContent(content);
					messages.add(message);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(null!=connection){
				try {
					connection.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return messages;


	}
	public static void testInit(WeiBo wb){
		wb.initNameSpace();
		wb.initTable();
	}
	public static  void testPublishContent(WeiBo wb){
		wb.publishContent("0001", "Tomorrow will be better!");
		wb.publishContent("0001", "Tomorrow will be better!");
	}
	public static void testAddAttends(WeiBo wb){
		wb.publishContent("0008", "今天天氣真不錯!!!");
		wb.publishContent("0009", "今天天氣真不錯!!!");
		wb.addAttends("0001","0008","0009");
	}
	public static void testRemoveAttends(WeiBo wb){
		wb.removeAttends("0001", "0009");
	}
	public static void testGetAttendsContents(WeiBo wb){
		List<Message> messages=wb.getAttendsContens("0001");
		for (Message message : messages) {
			System.out.println(message);
		}
	}
	public static void main(String[] args) {
		WeiBo wb=new WeiBo();
		//testInit(wb);
		//testPublishContent(wb);
		testAddAttends(wb);
		//testRemoveAttends(wb);

	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章