分享

淘宝hbase业务实践

hyj 2014-3-1 23:56:21 发表于 技术应用 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 0 17596
本帖最后由 hyj 于 2014-3-2 00:00 编辑

可以带着下面问题来阅读
1.HTablePool与传统数据块连接池那个对应?
2.通过那个函数可以释放连接池?
3.habse在什么情况下会比较慢?
4.RowKey该如何设计?


准备工作
  • HBase Toturial,需要对HBase有一定的了解
  • Mysql 基础,需要对Mysql有一定的了解
  • Java 基础,需要对Java有一点的了解

为什么需要HBase优势:
  • 再见了,分库分表。再见了,TDDL
  • 更高性能的读和写。

不足:
  • 没有SQL
  • 没有iBtatis、Hibernate这些ORM工具,HBase的ORM目前还不成熟
  • HBase的RowKey的设计要求比较高
  • 需要自己构建索引表

HBase的使用构建单例的HBaseFactory
构建单例的HBaseFactory,我们只需要关心三个事情
  • hbase.zookeeper.quorum
  • zookeeper.znode.parent
  • HTablePool的maxSize
我们使用的是HTablePool构建一个HBaseFactory对象

*为什么使用HTablePool
HTablePool您可以看成JDBC的连接池,适合多线程使用环境,如果需要把连接“还”给连接池的话,只需要调用HTableInterface.close() 就可以了
HBaseFactory的Interface
  1. public interface HBaseFactory {
  2.     /**
  3.      * 通过 tableName 来获取这个 Table
  4.      */
  5.     HTableInterface getHTable(String tableName);
  6.     /**
  7.      * 关闭某个table
  8.      */
  9.     void closeHTable(HTableInterface hTableInterface);
  10.     /** only for unit test*/
  11.     boolean deleteTable(String tableName);
  12.     /** only for unit test*/
  13.     HTableDescriptor createTable(String tableName, int maxVersion);
  14. }
复制代码
HBaseFactory的Implemention
  1. public class HBaseFactoryImpl implements HBaseFactory {
  2.     static Logger logger = LoggerFactory.getLogger(HBaseFactoryImpl.class);
  3.     private HTablePool hTablePool = null;
  4.     private HBaseAdmin hBaseAdmin = null;
  5.     @Inject
  6.     public HBaseFactoryImpl(String quorum, String parent, int maxSize) {
  7.         checkArgument(isNotBlank(quorum));
  8.         checkArgument(isNotBlank(parent));
  9.         Configuration conf = HBaseConfiguration.create();
  10.         conf.set("hbase.zookeeper.quorum", quorum);
  11.         conf.set("zookeeper.znode.parent", parent);
  12.         conf.set("hbase.client.retries.number", "5");
  13.         conf.set("hbase.client.pause", "200");
  14.         conf.set("ipc.ping.interval", "3000");
  15.         conf.setBoolean("hbase.ipc.client.tcpnodelay", true);
  16.         hTablePool = new HTablePool(conf, maxSize);
  17.         try {
  18.             hBaseAdmin = new HBaseAdmin(conf);
  19.         } catch (Exception e) {
  20.             logger.error(e.getMessage(), e);
  21.             throw new IllegalStateException(e);
  22.         }
  23.     }
  24.     @Override
  25.     public HBaseAdmin getHBaseAdmin() {
  26.         return checkNotNull(hBaseAdmin);
  27.     }
  28.     @Override
  29.     public HTableInterface getHTable(String tableName) {
  30.         checkArgument(isNotBlank(tableName));
  31.         return checkNotNull(hTablePool.getTable(tableName));
  32.     }
  33.     @Override
  34.     public void closeHTable(HTableInterface hTableInterface) {
  35.         Closeables.closeQuietly(hTableInterface);
  36.     }
  37.     @Override
  38.     public boolean deleteTable(String tableName) {
  39.         checkArgument(isNotBlank(tableName));
  40.         try {
  41.             hBaseAdmin.disableTable(tableName);
  42.             hBaseAdmin.deleteTable(tableName);
  43.         } catch (IOException e) {
  44.             logger.error(e.getMessage(), e);
  45.             return false;
  46.         }
  47.         return true;
  48.     }
  49.     @Override
  50.     public HTableDescriptor createTable(String tableName, int maxVersion) {
  51.         return createTable(tableName, "cf", 0, maxVersion, null, null,
  52.                 null, 0);
  53.     }
  54.     protected HTableDescriptor createTable(
  55.             String tableName, String columnFamily, int lifetime,
  56.             int maxVersion, StoreFile.BloomType bloomType, String startKey,
  57.             String endKey, int numRegions) {
  58.         try {
  59.             checkArgument(!checkNotNull(hBaseAdmin).tableExists(tableName),
  60.                     "the table [%s] should not exist.", tableName);
  61.         } catch (IOException e) {
  62.             logger.error(e.getMessage(), e);
  63.             throw new IllegalStateException(e);
  64.         }
  65.         HColumnDescriptor cf = getCF(columnFamily, lifetime, maxVersion,
  66.                 bloomType);
  67.         HTableDescriptor table = new HTableDescriptor(tableName);
  68.         table.addFamily(cf);
  69.         try {
  70.             if (StringUtils.isNotBlank(startKey)
  71.                     && StringUtils.isNotBlank(endKey) && numRegions > 0)
  72.                 hBaseAdmin.createTable(table, Bytes.toBytes(startKey),
  73.                         Bytes.toBytes(endKey), numRegions);
  74.             else
  75.                 hBaseAdmin.createTable(table);
  76.         } catch (IOException e) {
  77.             logger.error(e.getMessage(), e);
  78.             throw new IllegalStateException(e);
  79.         }
  80.         return describeTable(tableName);
  81.     }
  82.     private HColumnDescriptor getCF(String columnFamily, int lifetime,
  83.                                     int maxVersion, StoreFile.BloomType bloomType) {
  84.         HColumnDescriptor cf = new HColumnDescriptor(columnFamily);
  85.         cf.setCompactionCompressionType(Compression.Algorithm.LZO);
  86.         cf.setCompressionType(Compression.Algorithm.LZO);
  87.         if (maxVersion > 0)
  88.             cf.setMaxVersions(maxVersion > 1000000 ? 1000000 : maxVersion);
  89.         if (lifetime > 0)
  90.             cf.setTimeToLive(lifetime);
  91.         if (null != bloomType)
  92.             cf.setBloomFilterType(bloomType);
  93.         else
  94.             cf.setBloomFilterType(StoreFile.BloomType.ROW);
  95.         return cf;
  96.     }
  97.     public HTableDescriptor describeTable(String tableName) {
  98.         try {
  99.             return checkNotNull(hBaseAdmin).getTableDescriptor(Bytes.toBytes(tableName));
  100.         } catch (Exception e) {
  101.             logger.error(e.getMessage(), e);
  102.             throw new IllegalStateException(e);
  103.         }
  104.     }
  105.     @PreDestroy
  106.     public void destroy() throws Exception {
  107.         Closeables.closeQuietly(hTablePool);
  108.         Closeables.closeQuietly(hBaseAdmin);
  109.     }
  110. }
复制代码
Usage
  1. HTableInterface hTableInterface = null;
  2. try {
  3.     hTableInterface = hBaseFactory.getHTable("YOUR_TABLE_NAME");
  4.     // code here …
  5. } finally {
  6.     hBaseFactory.closeHTable(hTableInterface);
  7. }
复制代码

Scan

  • StartRow&Cache如果不设置StartRow,那就会从头开始搜索,这样做的话速度就会很慢
    Cache能够保证一次搜索拿到内存的数据,否则您iterator一次就得走一次网络
  • 关于FilterPrefixFilter是最常用的filter,有个非常需要注意的点
    如果Rowkey是”123_1_00000“这样的,如果prefix是123_1,切记切记要记得写成123_1_
    其次要注意filter不要太多,最好不要超过2个
  • 关于分页在Mysql里面,常常需要用到分页,那么在HBase里面你该如何实现,使用PageFilter配合startRow,但是在Mysql里面常常会有一个总数的概念,切记切记HBase里面不要做类似Count的操作
  • 关于分布式流式处理比方说,现在有10台机器,需要同时处理1000万的数据,那么这个时候,我们就可以用到checkAndPut。就像Mysql里面的一个乐观锁一样。

具体的做法是:

  • 我们通过PageFilter,SingleColumnValueFilter配合startRow获取一部分数据
  • 然后用checkAndPut标记该数据正在处理
  • 最后再用put标记该数据已经处理

HBase实战经验向下兼容

开发过程中,难免需要加字段的,那这个时候,就需要代码、数据能够向下兼容。
比方说我们现在需要新增一个column,因为是新加的一列,原来的数据这列就是null,那么这时候从HBase里面读到的值就是null,所以写HBase代码一定要注意:

  • 从HBase里面的数据一定要check null,如果是null,我就用一个默认值
  • 代表元数据的DO类的默认值,最好不要是null,null永远不要存在在代码中
  • 再一次强调,非常建议数据用String的方式存储,因为可视化的数据能够帮您解决很多问题
  1. // firstNonNull 是 google guava Objects#firstNonNull 的方法,如果方法第一个参数是null,就返回第二个参数
  2. Integer.parseInt(new String(firstNonNull(result.getValue(DEFAULT_COLUMN_FAMILIES, COLUMN), new byte[]{'0'})));
复制代码
RowKey的设计

1.建议使用String如果不是特殊要求,RowKey最好都是String。
  • 方便线上使用Shell查数据、排查错误
  • 更容易让数据均匀分布
  • 不必考虑存储成本

2.RowKey的长度尽量短如果RowKey太长话,第一是,存储开销会增加,影响存储效率;第二是,内存中Rowkey字段过长,内存的利用率会降低,这会降低索引命中率。
一般的做法是:
  • 时间使用Long来表示
  • 尽量使用编码压缩
3.RowKey尽量散列RowKey的设计,最重要的是要保证散列,这样就会保证所有的数据都不都是在一个region上,避免做读写的时候负载将会集中在个别region上面。

假设我们需要存储一个用户的所有微博(暂时不需要考虑时间倒排),这时候的RowKey的设计是UserId_WeiboId ,但是这样设计的话,UserId 的分布就很可能不均匀,因为RowKey是字符串排序的。

有两种办法来解决这个问题

Reverses
UserId

Hash或者Mod
UserId

MD5 后作取前6位为前缀加入到 UserId 前面


4.RowKey排序假设我们有个很多微博用户发微博,但是这个时候,我们要开辟一个“广场”,所有的微博都是按照时间倒排序展示在这个“广场”里。这个时候我们就得为原来的

UserId_WeiboId

建立一张索引表,并且这个表的Rowkey要和时间相关

  • Rowkey的设计可以使用

当前时间 - 微博发表时间

的 long 值作为 RowKey 的前缀

  • RowKey散列


  • 如果数据可以定期清理如果数据不是需要一直保存的话,就算所有数据落在一个region,因为按时间搜索会指定startRow,存储时候Rowkey也是连续的,所以速度也非常块,当然数据容量最好和DBA商量一下


  • 如果数据都需要保存把DayOfMonth作为前缀那么RowKey会是
    DayOfMonth_(当前时间 - 微博发表时间)

    不过这样在代码实现上面的时候会有一些麻烦。

5.关于事务目前HBase的Put,Delete操作都是事务的,但是如果您希望能够对好几个Table发起一连串操作并且希望是事务的话,目前还没有好的办法。所以HBase使用的时候,要有解决数据出错的觉悟。




没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条