Apache Sparkは、オープンソースで高速かつ汎用的なビッグデータ処理エンジンであり、大規模データセットのバッチ処理およびストリーム処理を専門としています。Hadoopエコシステムにおいて中核的な役割を果たしていますが、従来のMapReduceモデルと比較して、Sparkはより効率的なデータ処理能力を提供します。
本記事では、Sparkを使用してOBKV-HBaseに直接アクセスする方法について説明します。コードの変更は一切不要です。
前提条件
Sparkを使用してOBKV-HBaseにアクセスする前に、以下の条件を満たしていることを確認してください:
- OBKV-HBase Javaクライアントのバージョン2.2.1/1.2.1以降を使用していること。これにより、Sparkのネイティブ機能をサポートします。
- OceanBaseのバージョンV4.4.1以降を使用していること。これにより、OBKV-HBaseがSparkを使用できるようになります。
- Spark-HBaseの依存関係を導入する際は、Spark-HBaseに付属するHBaseの依存関係を除外してください。これにより、OBKV-HBase Javaクライアントの依存関係に含まれるHBaseバージョンとの競合を防ぎます。
- Sparkのバージョンが3.0.1以下であることを確認してください。
テーブルの作成
Sparkを使用してOBKV-HBaseワイドテーブルにアクセスする場合、テーブルの作成方法は以下の2つです。
- HBase Admin createTableインターフェース を使用してテーブルを作成します。
- SQLを使用してテーブルを作成します。SQLでテーブルを作成する場合は、OBKV-HBaseのテーブル作成ルールに従って行う必要があります。詳細なルールについては、OBKV-HBaseデータ操作例を参照してください。
OBKV-HBaseクライアント接続パラメータの設定
OBKV-HBaseクライアント接続パラメータは、OceanBaseクラスタとの接続を確立するために使用されます。OBKV-HBaseでは、パブリッククラウドとプライベートデプロイメントの方式によって、クライアント接続パラメータの設定方法が異なります。プライベートデプロイメントのOceanBaseクラスタについては、直接接続モードの設定に従って設定する必要がありますが、パブリッククラウドのOBKVサービスについては、クラウドモードに従って設定する必要があります。具体的なOBKV-HBaseクライアント接続クラスタのチュートリアルについては、OBKV-HBaseクライアントを使用したクラスタへの接続を参照してください。
どのようなデプロイメント方式であっても、すべての構成パラメータは以下の2つの方法で設定できます:
- 設定ファイル
hbase-site.xml内で設定します。 - Hadoop Configurationの
setメソッドを使用し、コード内でConnectionを初期化する前に設定します。
直接接続モードの設定
直接接続モードは、プライベートデプロイメントのOceanBaseクラスタに適用されます。Configurlを使用してOceanBaseクラスタに直接接続します。Configurlは、OceanBaseデータベースの基盤となるサーバーの実際のアドレスを取得するために使用されます。以下は、Sparkで直接接続モードの接続パラメータを設定する方法です:
注意
paramURLに含まれるXMLの特殊文字&は、設定ファイル内では&と記述してください。エスケープしないとXMLの解析に失敗します。
方法1:
hbase-site.xml設定ファイルに以下のパラメータを設定します:<!-- 現在のクラスタを以下のように仮定します ClusterName:obkvcluster TenantName:obkv DataBaseName: test UserName:root UserPassWord: SYS_USER_NAME : sysroot SYS_USER_PASSWD: --> <configuration> <!-- OceanBase HBase実装クラスの置き換え --> <property> <name>hbase.client.connection.impl</name> <value>com.alipay.oceanbase.hbase.util.OHConnectionImpl</value> </property> <!-- OceanBase接続設定 --> <property> <!-- obconfig serverからRSlistのURLを取得します。詳細は後述のConfig URLの取得を参照してください。--> <name>hbase.oceanbase.paramURL</name> <value>PARAM_URL</value> </property> <property> <!-- 形式はuserName@tenantName#clusterName --> <name>hbase.oceanbase.fullUserName</name> <value>root@obkv#obkvcluster</value> </property> <property> <!-- fullUserNameで指定したユーザーがOceanBaseにアクセスするためのパスワード --> <name>hbase.oceanbase.password</name> <value></value> </property> <property> <!-- システムテナント配下のユーザー名。ルーティングテーブルにアクセスする権限があるのはシステムテナント配下のユーザーのみです --> <name>hbase.oceanbase.sysUserName</name> <value>sysroot</value> </property> <property> <!-- システムテナント配下のユーザーパスワード --> <name>hbase.oceanbase.sysPassword</name> <value></value> </property> </configuration>方法2:コード内でHadoop Configurationにパラメータを設定し、
setメソッドを使用してコード内のConnection初期化前に設定します:/* * 現在のクラスタを以下のように仮定します * ClusterName:obkvcluster * TenantName:obkv * DataBaseName: test * UserName:root * UserPassWord: * SYS_USER_NAME : sysroot * SYS_USER_PASSWD: */ Configuration conf = HBaseConfiguration.create(); conf.set("hbase.client.connection.impl", "com.alipay.oceanbase.hbase.util.OHConnectionImpl") conf.set("hbase.oceanbase.fullUserName", "root@obkv#obkvcluster"); conf.set("hbase.oceanbase.password", ""); // obconfig serverからRSlistのURLを取得します。詳細は後述のConfig URLの取得を参照してください。 conf.set("hbase.oceanbase.paramURL", ""); conf.set("hbase.oceanbase.sysUserName", "sysroot"); conf.set("hbase.oceanbase.sysPassword", "");
Config URLの取得:
以下の方法でConfig URLを取得できます:
OCPからConfig URLを取得する
OCPを使用している場合は、OCPからConfig URLを取得できます。具体的な操作手順は以下のとおりです:
a. OceanBase Cloud Platform(OCP)にログインします。 b. 左側のナビゲーションバーでクラスタを選択し、スクロールしてクラスタリストを見つけ、アクセスするクラスタ名をクリックして開きます。 c. クラスタの詳細ページでConfigURLパラメータを見つけます。このパラメータはクライアントの初期化時に設定する必要があります。
OBDを使用してConfig Serverをデプロイし、ObRootServiceInfoUrlを取得する
OCPを使用していない場合は、OBDを使用してConfig Serverをデプロイする必要があり、
ObRootServiceInfoUrlを取得する必要があります。
クラウドモードの設定
クラウドモードは、パブリッククラウドにデプロイされたOceanBaseクラスタに適用されます。ODPを介してOceanBaseクラスタに接続します。以下はSparkでクラウドモードの接続パラメータを設定する方法です:
方法1:
hbase-site.xml設定ファイルに以下のパラメータを設定します:<!-- 現在のクラスタを次のように仮定します DataBaseName: test UserName:root <configuration> --> <!-- OceanBase HBase実装クラスの置換 --> <property> <name>hbase.client.connection.impl</name> <value>com.alipay.oceanbase.hbase.util.OHConnectionImpl</value> </property> <!-- OceanBase接続設定 --> <configuration> <property> <name>hbase.oceanbase.fullUserName</name> <value>root</value> </property> <property> <name>hbase.oceanbase.password</name> <value></value> </property> <property> <!-- ODP Addressの取得については下記を参照 --> <name>hbase.oceanbase.odpAddr</name> <value></value> </property> <property> <name>hbase.oceanbase.odpPort</name> <value>3307</value> </property> <property> <name>hbase.oceanbase.odpMode</name> <value>true</value> </property> <property> <name>hbase.oceanbase.database</name> <value>test</value> </property> </configuration>方法2:コード内でHadoop Configurationにパラメータを設定し、コード内のConnection初期化前にsetメソッドで設定します:
/*
* 現在のクラスタを次のように仮定します
* DataBaseName: test
* UserName:root
*/
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.client.connection.impl", "com.alipay.oceanbase.hbase.util.OHConnectionImpl")
conf.set(HBASE_OCEANBASE_FULL_USER_NAME, "root");
// ユーザーのパスワード
conf.set(HBASE_OCEANBASE_PASSWORD, "");
// ODP Addressの備考を参照
conf.set(HBASE_OCEANBASE_ODP_ADDR, "");
// OBKVのポートは3307(固定)
conf.setInt(HBASE_OCEANBASE_ODP_PORT, "3307");
// クラウドではODPモードを使用します(固定)
conf.setBoolean(HBASE_OCEANBASE_ODP_MODE, true);
// データベースの名前
conf.set(HBASE_OCEANBASE_DATABASE, "test");
使用例
依存関係の導入
OBKV-HBaseの依存関係を導入します:
<!-- OBKV-HBaseの依存関係には、hbase-clientとhadoop-commonが含まれます -->
<dependency>
<groupId>com.oceanbase</groupId>
<artifactId>obkv-hbase-client</artifactId>
<version>2.2.1</version>
</dependency>
OBKV-HBaseとSparkのシームレスな統合を確保するため、Spark 3.0.1バージョンの依存関係を使用し、hbase-spark依存関係と統合することを推奨します。Sparkの依存関係を導入する際には、以下の点に注意してください:
hbase-spark依存関係を導入する際には、Sparkに組み込まれているhbase-clientとhadoop-common依存関係を除外する必要があります。これにより、OBKV-HBaseの依存関係との競合を回避できます。- OBKV-HBaseの依存関係を導入する際には、
jackson-databind依存関係を除外する必要があります。これにより、Sparkで使用されている対応するバージョンとの競合を避け、バージョンチェックに失敗するのを防ぎます。
<!-- OBKV-HBaseの依存関係 -->
<dependency>
<groupId>com.oceanbase</groupId>
<artifactId>obkv-hbase-client</artifactId>
<version>${hbase.client.version}</version>
<exclusions>
<exclusion>
<artifactId>jackson-databind</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- Sparkの依存関係 -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.12</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.12</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.12</artifactId>
<version>${spark.version}</version>
</dependency>
<!-- SparkとHBaseコネクタの依存関係 -->
<dependency>
<groupId>org.apache.hbase.connectors.spark</groupId>
<artifactId>hbase-spark</artifactId>
<version>${hbase.spark.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
</exclusion>
</exclusions>
</dependency>
Sparkによる読み書きの例
以下は、SparkからOBKV-HBaseに読み書きする例です。テーブル構造、データの読み書き、およびSpark SQLによるデータの読み書きが含まれます。
テーブル構造
現在、OBKV-HBaseテーブルが存在すると仮定します:
-- テーブルグループを作成
CREATE TABLEGROUP `spark_demo` SHARDING = 'ADAPTIVE';
-- テーブルを作成
CREATE TABLE `spark_demo$cf1` (
`K` varbinary(1024) NOT NULL,
`Q` varbinary(256) NOT NULL,
`T` bigint(20) NOT NULL,
`V` varbinary(1024) DEFAULT NULL,
PRIMARY KEY (`K`, `Q`, `T`)
) TABLEGROUP = `spark_demo` partition by key(`K`) partitions 17;
CREATE TABLE `spark_demo$cf2` (
`K` varbinary(1024) NOT NULL,
`Q` varbinary(256) NOT NULL,
`T` bigint(20) NOT NULL,
`V` varbinary(1024) DEFAULT NULL,
PRIMARY KEY (`K`, `Q`, `T`)
) TABLEGROUP = `spark_demo` partition by key(`K`) partitions 17;
HadoopのTableInputFormat/TableOutputFormatとSpark RDDを使用したデータの読み書き
HadoopのTableInputFormatとTableOutputFormat、およびSparkのRDDを組み合わせて使用することで、データの読み書き操作を実現します。SparkはOBKV-HBaseのJava APIを使用して読み書きを行うため、hbase.zookeeper.quorumの設定は不要です。この設定方法により、OceanBaseの分散クラスタ機能を最大限に活用できます。
// データの書き込み
public void testSparkReadAfterWriteHBase() throws Exception {
// SparkContextの初期化
SparkConf sparkConf = new SparkConf().setAppName("HBaseWriteExample").setMaster("local");
JavaSparkContext jsc = new JavaSparkContext(sparkConf);
// 書き込み用のHBase設定
Configuration hbaseConf = HBaseConfiguration.create();
hbaseConf.set(TableOutputFormat.OUTPUT_TABLE, "spark_demo");
hbaseConf.set("mapreduce.job.output.key.class", ImmutableBytesWritable.class.getName());
hbaseConf.set("mapreduce.job.output.value.class", Put.class.getName());
hbaseConf.set("mapreduce.outputformat.class", TableOutputFormat.class.getName());
// RDDデータの作成(Key: RowKey, Value: Tuple of (ColumnFamily, ColumnName, Value)))
JavaRDD<Tuple2<byte[], Tuple3<String, String, String>>> data = jsc.parallelize(Arrays.asList(
new Tuple2<>(Bytes.toBytes(row1), new Tuple3<>(family1, "name", john_name)),
new Tuple2<>(Bytes.toBytes(row1), new Tuple3<>(family1, "age", john_age)),
new Tuple2<>(Bytes.toBytes(row1), new Tuple3<>(family1, "gender", john_gender)),
new Tuple2<>(Bytes.toBytes(row2), new Tuple3<>(family1, "name", lily_name)),
new Tuple2<>(Bytes.toBytes(row2), new Tuple3<>(family1, "age", lily_age)),
new Tuple2<>(Bytes.toBytes(row2), new Tuple3<>(family1, "gender", lily_gender)),
new Tuple2<>(Bytes.toBytes(row3), new Tuple3<>(family1, "name", walter_name)),
new Tuple2<>(Bytes.toBytes(row3), new Tuple3<>(family1, "age", walter_age)),
new Tuple2<>(Bytes.toBytes(row3), new Tuple3<>(family1, "gender", walter_gender))
));
// RDDをHBase形式(ImmutableBytesWritable, Put)にマッピング
JavaPairRDD<ImmutableBytesWritable, Put> hbaseWriteRDD = data.mapToPair(tuple -> {
byte[] rowKey = tuple._1;
String family = tuple._2._1();
String qualifier = tuple._2._2();
String value = tuple._2._3();
Put put = new Put(rowKey);
put.addColumn(Bytes.toBytes(family), Bytes.toBytes(qualifier), Bytes.toBytes(value));
return new Tuple2<>(new ImmutableBytesWritable(rowKey), put);
});
// データをHBaseに書き込む
hbaseWriteRDD.saveAsNewAPIHadoopDataset(hbaseConf);
// HBaseから読み取る
hbaseConf.set(TableInputFormat.INPUT_TABLE, "spark_demo");
Scan scan = new Scan();
scan.addFamily(Bytes.toBytes(family1));
scan.addFamily(Bytes.toBytes(family2));
scan.setCaching(100);
String scanToString = org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil.convertScanToString(scan);
hbaseConf.set(TableInputFormat.SCAN, scanToString);
JavaPairRDD<ImmutableBytesWritable, Result> hbaseReadRDD = jsc.newAPIHadoopRDD(
hbaseConf,
TableInputFormat.class,
ImmutableBytesWritable.class,
Result.class
);
// データの処理
hbaseReadRDD.flatMapToPair(tuple -> {
ImmutableBytesWritable key = tuple._1();
Result result = tuple._2();
String rowKey = Bytes.toString(key.get());
List<Tuple2<String, String>> res = new ArrayList<>();
for (Cell c : result.rawCells()) {
String familyValue = Bytes.toString(CellUtil.cloneValue(c));
String column = Bytes.toString(CellUtil.cloneQualifier(c));
Tuple2<String, String> value = new Tuple2<>(rowKey, String.format("Get result from cf1: K: %s, Q: %s, V: %s", Bytes.toString(result.getRow()), column, familyValue));
res.add(value);
}
return res.iterator();
}).collect().forEach(System.out::println);
// SparkContextを停止
jsc.stop();
}
SparkSession SQLによるデータの読み取り
Spark SQLはHBaseからのデータ読み取りのみをサポートしています。OBKV-HBaseは現在、SparkSQLPushDownFilterフィルターをサポートしていません。このフィルターを無効にするには、SparkSessionのhbase.spark.pushdown.columnfilterパラメータをfalseに設定する必要があります。このパラメータのデフォルト値はtrueです。例:
public void testSparkSessionSQLHBase() throws Exception {
SparkConf conf = new SparkConf().setAppName("SparkSQLHBaseExample").setMaster("local[*]");
conf.set("spark.sql.defaultUrlStreamHandlerFactory.enabled", "false"); // avoid Connection Casting Exception
JavaSparkContext jsc = new JavaSparkContext(conf);
// Initialize SparkSession
SparkSession spark = SparkSession.builder()
.appName("SparkSQLHBaseExample")
.config(conf)
.getOrCreate();
// HBase configuration
Configuration hbaseConf = ObHTableTestUtil.newConfiguration();
// Initialize HBaseContext
String tmpHdfsConfigPath = "/your-hdfs-path/tmp/hbase-config";
HBaseContext hbaseContext = new HBaseContext(jsc.sc(), hbaseConf, tmpHdfsConfigPath);
String tableName = "spark_demo";
String hbase_column_mapping = "id STRING :key, " +
"name STRING cf1:name, " +
"gender STRING cf1:gender, " +
"age STRING cf1:age";
Dataset<Row> hbaseDF = spark.read()
.format("org.apache.hadoop.hbase.spark")
.option("hbase.columns.mapping", hbase_column_mapping)
// disable column pushdown optimization,OBKV-HBase does not support SparkSQLPushDownFilter yet
.option("hbase.spark.pushdown.columnfilter", false)
.option("hbase.table", tableName)
.load();
hbaseDF.printSchema();
// select * from table
hbaseDF.show();
// Perform SQL queries on the DataFrame
hbaseDF.createOrReplaceTempView(tableName);
// select with condition
Dataset<Row> result = spark.sql("SELECT * FROM spark_demo WHERE age > 30");
result.show();
jsc.stop();
spark.stop();
}