HBase的代码是基于1.2.6的
客户端的代码如下:
public class CreateTable {
public static void main(String[] args) throws IOException, ServiceException {
// new一个配置对象
Configuration conf = HBaseConfiguration.create();
Connection conn = ConnectionFactory.createConnection(conf);
HBaseAdmin.checkHBaseAvailable(conf);
Admin admin = conn.getAdmin();
// 初始化表描述
HTableDescriptor tableDescriptor = new HTableDescriptor(
TableName.valueOf("student"));
// 给表描述对象增加列族
tableDescriptor.addFamily(new HColumnDescriptor("name"));
tableDescriptor.addFamily(new HColumnDescriptor("age"));
// 让admin根据tableDescriptor执行建表操作
admin.createTable(tableDescriptor);
System.out.println("hbase表创建成功! 表名为emp,列族有personal , professional");
}
}
客户端执行代码createTable()之后,这个消息会通过RPC的方式,调用HMaster::createTable()函数。其整体流程中如下:
客户端通过RPC调用HMaster的createTable()函数。在这个函数中,
public long createTable() throws IOException {
if (isStopped()) {
throw new MasterNotRunningException();
}
String namespace = hTableDescriptor.getTableName().getNamespaceAsString();
ensureNamespaceExists(namespace);
final HRegionInfo[] newRegions =
ModifyRegionUtils.createHRegionInfos(hTableDescriptor, splitKeys);
checkInitialized();
sanityCheckTableDescriptor(hTableDescriptor);
return MasterProcedureUtil.submitProcedure(
new MasterProcedureUtil.NonceProcedureRunnable(this, nonceGroup, nonce) {
@Override
protected void run() throws IOException {
getMaster().getMasterCoprocessorHost().preCreateTable(hTableDescriptor, newRegions);
LOG.info(getClientIdAuditPrefix() + " create " + hTableDescriptor);
// TODO: We can handle/merge duplicate requests, and differentiate the case of
// TableExistsException by saying if the schema is the same or not.
ProcedurePrepareLatch latch = ProcedurePrepareLatch.createLatch();
submitProcedure(new CreateTableProcedure(
procedureExecutor.getEnvironment(), hTableDescriptor, newRegions, latch));
latch.await();
getMaster().getMasterCoprocessorHost().postCreateTable(hTableDescriptor, newRegions);
}
@Override
protected String getDescription() {
return "CreateTableProcedure";
}
});
}
1)客户端的请求,会调用HMaster::CreateTable函数,在这个函数会会创建一个CreateTablePorcedure对象(Procedure的实现), 并调用submitPorcedure
2)在submitProcedure中,会将这个Procedure放入到一个Set集 – runnables中。
3) HMaster的ProcedureExecuteor会调动一个线程,这个线程执行函数execLoop(),这个函数会不断扫描runnable这个对象,如果有数据,就会从中取出来,然后调用函数execProcedure()去处理这个Procedure
4)而execProcedure,最终会调用CreateTableProcedure::executeFromState()函数去处理。
5)当一切处理完成姤 ,就执行退出。显示创建table成功.
上面已经说过了,创建表的过程的实际动作是在executeFromState中完成的。
而此函数的流程图如下:
这里选择其中的几个重要步骤进行说明。
创建表时,需要先创建Region,然后再部署到相应的RegionServer上去,这是两部分。其中创建Region是在CREATE_TABLE_WRITE_FS_LAYOUT中实现的。
其对应的函数是CreateTableProcedure::createFsLayout(),其代码如下:
protected static List createFsLayout(...) throws IOException {
final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
final Path tempdir = mfs.getTempDir();
// 1. Create Table Descriptor
// using a copy of descriptor, table will be created enabling first
final Path tempTableDir = FSUtils.getTableDir(tempdir, hTableDescriptor.getTableName());
new FSTableDescriptors(env.getMasterConfiguration()).createTableDescriptorForTableDirectory(
tempTableDir, hTableDescriptor, false);
// 2. Create Regions
newRegions = hdfsRegionHandler.createHdfsRegions(env, tempdir,
hTableDescriptor.getTableName(), newRegions);
// 3. Move Table temp directory to the hbase root location 创建相应的HDFS目录
... ...
return newRegions;
}
从上面注释可以看到,这一过程分为三个部分:创建Table的Descriptor,创建Region信息, 最后创建相应的HDFS目录
创建完了Region之后,就需要将这个Region在实际的RegionServer上面部署起来。它会调用
protected static void assignRegions(final MasterProcedureEnv env,
final TableName tableName, final List regions)
throws HBaseException, IOException {
ProcedureSyncWait.waitRegionServers(env);
final AssignmentManager assignmentManager = env.getMasterServices().getAssignmentManager();
// Mark the table as Enabling 设置zookeeper中的状态信息
assignmentManager.getTableStateManager().setTableState(tableName,
ZooKeeperProtos.Table.State.ENABLING);
// Trigger immediate assignment of the regions in round-robin fashion
// 部署Region
ModifyRegionUtils.assignRegions(assignmentManager, regions);
// Enable table
assignmentManager.getTableStateManager()
.setTableState(tableName, ZooKeeperProtos.Table.State.ENABLED);
}
再看一下assignRegions的实现。
下面是AssignRegions中的调用栈过程,其中sendRegionOpen()就是发送到RegionServer上去执行。
org.apache.hadoop.hbase.master.ServerManager ServerManager.java:sendRegionOpen 800
org.apache.hadoop.hbase.master.AssignmentManager AssignmentManager.java:assign 1739
org.apache.hadoop.hbase.master.AssignmentManager AssignmentManager.java:assign 2851
org.apache.hadoop.hbase.master.AssignmentManager AssignmentManager.java:assign 2830
org.apache.hadoop.hbase.util.ModifyRegionUtils ModifyRegionUtils.java:assignRegions 287
org.apache.hadoop.hbase.master.procedure.CreateTableProcedure CreateTableProcedure.java:assignRegions 452
org.apache.hadoop.hbase.master.procedure.CreateTableProcedure CreateTableProcedure.java:executeFromState 127
org.apache.hadoop.hbase.master.procedure.CreateTableProcedure CreateTableProcedure.java:executeFromState 58
org.apache.hadoop.hbase.procedure2.StateMachineProcedure StateMachineProcedure.java:execute 119
org.apache.hadoop.hbase.procedure2.Procedure Procedure.java:doExecute 498
org.apache.hadoop.hbase.procedure2.ProcedureExecutor ProcedureExecutor.java:execProcedure 1152
org.apache.hadoop.hbase.procedure2.ProcedureExecutor ProcedureExecutor.java:execLoop 947
org.apache.hadoop.hbase.procedure2.ProcedureExecutor ProcedureExecutor.java:execLoop 900
org.apache.hadoop.hbase.procedure2.ProcedureExecutor ProcedureExecutor.java:access$400 77
org.apache.hadoop.hbase.procedure2.ProcedureExecutor$2 ProcedureExecutor.java:run 497
具体看一下assign中过程。这里有两个过程,需要关注:Master是如何选择RegionServer,以及Region如何部署到RegionServer上去的。
查看AssignmentManager::assign()的代码可看到:
public void assign(List regions)
throws IOException, InterruptedException {
...
//首先找出现在属于online状态的regionserver,将这些regionserver放到list列表中
List servers = serverManager.createDestinationServersList();
...
// Generate a round-robin bulk assignment plan
//默认的情况下,是调用BaseLoadBalancer::roundRobinAssignment()函数
Map> bulkPlan = balancer.roundRobinAssignment()(regions, servers);
...
processFavoredNodes(regions);
assign(regions.size(), servers.size(), "round-robin=true", bulkPlan);
}
进一步分析函数BaseLoadBalancer::roundRobinAssignment()
private void roundRobinAssignment(...) {
int numServers = servers.size();
int numRegions = regions.size();
//设置regions数与numServers的比例,即一个Server上面有多少个regions
int max = (int) Math.ceil((float) numRegions / numServers);
int serverIdx = 0;
for (int j = 0; j < numServers; j++) {
//按server进行分配regions
ServerName server = servers.get((j + serverIdx) % numServers);
List serverRegions = new ArrayList(max);
//需要注意的是这里的i的步进是server的个数。让region分散在各个server上面
for (int i = regionIdx; i < numRegions; i += numServers) {
//取出一个region
HRegionInfo region = regions.get(i % numRegions);
。。。 。。。
serverRegions.add(region);
cluster.doAssignRegion(region, server);
}
}
//将分配好的region与server的关系放到assignments的map中
assignments.put(server, serverRegions);
regionIdx++;
}
}
HBase创建表的时候,分为多个步骤,通过不同步骤的状态量进行程序的执行。所有的动作也是在CreateTableProcedure::executeFromState() 完成的。