HBase 환경에서 데이터가 리전에 고르게 분포되지 않을 경우, 특히 예비 분할 없이 생성된 리전일 경우, 쓰기 부하의 불균형 문제가 발생할 수 있습니다. 전통적인 해시 또는 솔트 방식은 분산을 개선할 수 있지만, 이후 조회 성능에 영향을 미치는 문제점이 있습니다.
본 문서에서는 테이블 단위에서 리전의 분포를 최적화하는 새로운 균형 조정 전략을 제안합니다. 이 방법은 HBase 관리자 인터페이스의 기본 balancer() 함수를 대체하며, 각 리전 서버의 리전 수가 평균값 근처로 유지되도록 합니다.
다음은 필요한 의존성 설정입니다:
<repositories>
<repository>
<id>cloudera</id>
<url>https://repository.cloudera.com/artifactory/cloudera-repos</url>
</repository>
</repositories>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.2.1</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>1.2.1</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
주요 로직 구현 코드는 다음과 같습니다:
package hbase_balance;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
import java.util.*;
/**
* 테이블 단위 리전 균형 조정 전략
* - 각 리전 서버의 리전 수가 평균치에 가까워지도록 재배치
* - 기존 admin.balancer() 대신 사용 가능
*/
public class HbaseBalancer {
public static final String TABLE_NAME = "data1";
public static final String ZK_QUORUM = "hadoop01:2181,hadoop02:2181,hadoop03:2181";
public static final Integer REGIONS_PER_SERVER = 50;
public static void main(String[] args) throws IOException {
Configuration config = HBaseConfiguration.create();
config.set(HConstants.ZOOKEEPER_QUORUM, ZK_QUORUM);
Connection connection = ConnectionFactory.createConnection(config);
Admin admin = connection.getAdmin();
ClusterStatus clusterStatus = admin.getClusterStatus();
Collection<ServerName> serverList = clusterStatus.getServers();
System.out.println("리전 서버 목록:");
Map<String, RegionServerInfo> regionServerMap = new HashMap<>();
for (ServerName server : serverList) {
RegionServerInfo rsInfo = new RegionServerInfo();
rsInfo.setServerName(server);
regionServerMap.put(server.getHostname(), rsInfo);
System.out.println(server.getStartcode() + " " + server.getHostname() + " " + server.getHostAndPort());
}
Table table = connection.getTable(TableName.valueOf(TABLE_NAME));
RegionLocator locator = connection.getRegionLocator(table.getName());
List<HRegionLocation> regionLocations = locator.getAllRegionLocations();
int avgRegionCount = regionLocations.size() / serverList.size();
System.out.println("평균 리전 수: " + avgRegionCount);
List<HRegionLocation> toMove = new ArrayList<>();
System.out.println("=== 리전 위치 정보 수집 완료 ===");
// 현재 리전 배치 상태 반영
for (HRegionLocation loc : regionLocations) {
String host = loc.getHostname();
RegionServerInfo rs = regionServerMap.get(host);
if (rs.getRegionCount() < avgRegionCount) {
rs.addRegion(loc.getRegionInfo().getRegionNameAsString());
} else {
toMove.add(loc);
}
}
System.out.println("=== 리전 재배치 시작 ===");
Iterator<HRegionLocation> iterator = toMove.iterator();
while (iterator.hasNext()) {
HRegionLocation region = iterator.next();
HRegionInfo info = region.getRegionInfo();
byte[] encodedName = info.getEncodedNameAsBytes();
String sourceHost = region.getHostname();
// 가장 적은 리전 수를 가진 서버 선택
String targetHost = findMinRegionServer(regionServerMap);
RegionServerInfo targetRs = regionServerMap.get(targetHost);
if (targetRs.getRegionCount() >= avgRegionCount) {
break; // 평균 이상으로 증가하지 않도록 조건 체크
}
String destServerName = targetRs.getServerName().getServerName();
admin.move(encodedName, Bytes.toBytes(destServerName));
targetRs.addRegion(info.getRegionNameAsString());
System.out.println("리전 이동: " + info.getRegionNameAsString() + " → " + destServerName);
}
System.out.println("=== 리전 재배치 완료 ===");
}
/**
* 리전 수가 가장 적은 리전 서버의 호스트 이름 반환
*/
private static String findMinRegionServer(Map<String, RegionServerInfo> map) {
String minHost = null;
int minCount = Integer.MAX_VALUE;
for (Map.Entry<String, RegionServerInfo> entry : map.entrySet()) {
int count = entry.getValue().getRegionCount();
if (count < minCount) {
minCount = count;
minHost = entry.getKey();
}
}
return minHost;
}
}
리전 서버 정보를 관리하기 위한 보조 클래스입니다:
package hbase_balance;
import org.apache.hadoop.hbase.ServerName;
import java.util.ArrayList;
import java.util.List;
public class RegionServerInfo {
private ServerName serverName;
private List<String> regionNames;
public RegionServerInfo() {
this.regionNames = new ArrayList<>();
}
public ServerName getServerName() {
return serverName;
}
public void setServerName(ServerName serverName) {
this.serverName = serverName;
}
public List<String> getRegionNames() {
return regionNames;
}
public int getRegionCount() {
return regionNames.size();
}
public void addRegion(String regionName) {
regionNames.add(regionName);
}
}