From d55ac2bb1dfb66ab1d2a67a7d67e2f33790c7f5f Mon Sep 17 00:00:00 2001 From: Hobo Chen <hobochen96@live.com> Date: Tue, 4 Jun 2019 11:09:18 +0800 Subject: [PATCH] [tablestore] Add binding for Alibaba Cloud's TableStore (#1286) --- bin/bindings.properties | 2 +- bin/ycsb | 1 + distribution/pom.xml | 5 + pom.xml | 2 + tablestore/README.md | 61 ++++ tablestore/conf/tablestore.properties | 33 +++ tablestore/pom.xml | 34 +++ .../ycsb/db/tablestore/TableStoreClient.java | 276 ++++++++++++++++++ .../ycsb/db/tablestore/package-info.java | 22 ++ .../src/main/resources/log4j.properties | 25 ++ 10 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 tablestore/README.md create mode 100644 tablestore/conf/tablestore.properties create mode 100755 tablestore/pom.xml create mode 100755 tablestore/src/main/java/com/yahoo/ycsb/db/tablestore/TableStoreClient.java create mode 100644 tablestore/src/main/java/com/yahoo/ycsb/db/tablestore/package-info.java create mode 100644 tablestore/src/main/resources/log4j.properties diff --git a/bin/bindings.properties b/bin/bindings.properties index cd60158c..ec6262b4 100644 --- a/bin/bindings.properties +++ b/bin/bindings.properties @@ -78,4 +78,4 @@ s3:com.yahoo.ycsb.db.S3Client solr:com.yahoo.ycsb.db.solr.SolrClient solr6:com.yahoo.ycsb.db.solr6.SolrClient tarantool:com.yahoo.ycsb.db.TarantoolClient - +tablestore:com.yahoo.ycsb.db.tablestore.TableStoreClient diff --git a/bin/ycsb b/bin/ycsb index dfb105cd..263f4f6f 100755 --- a/bin/ycsb +++ b/bin/ycsb @@ -106,6 +106,7 @@ DATABASES = { "solr" : "com.yahoo.ycsb.db.solr.SolrClient", "solr6" : "com.yahoo.ycsb.db.solr6.SolrClient", "tarantool" : "com.yahoo.ycsb.db.TarantoolClient", + "tablestore" : "com.yahoo.ycsb.db.tablestore.TableStoreClient" } OPTIONS = { diff --git a/distribution/pom.xml b/distribution/pom.xml index 69f02b88..3a4003a2 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -269,6 +269,11 @@ LICENSE file. <artifactId>tarantool-binding</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>com.yahoo.ycsb</groupId> + <artifactId>tablestore-binding</artifactId> + <version>${project.version}</version> + </dependency> <!-- <dependency> <groupId>com.yahoo.ycsb</groupId> diff --git a/pom.xml b/pom.xml index 3a347332..96f955b6 100644 --- a/pom.xml +++ b/pom.xml @@ -108,6 +108,7 @@ LICENSE file. <tarantool.version>1.6.5</tarantool.version> <thrift.version>0.8.0</thrift.version> <voldemort.version>0.81</voldemort.version> + <tablestore.version>4.8.0</tablestore.version> </properties> <modules> @@ -163,6 +164,7 @@ LICENSE file. <module>solr</module> <module>solr6</module> <module>tarantool</module> + <module>tablestore</module> <!--<module>voldemort</module>--> </modules> diff --git a/tablestore/README.md b/tablestore/README.md new file mode 100644 index 00000000..c356df10 --- /dev/null +++ b/tablestore/README.md @@ -0,0 +1,61 @@ +<!-- +Copyright (c) 2018 YCSB contributors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you +may not use this file except in compliance with the License. You +may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. See accompanying +LICENSE file. +--> + +# Alibaba Cloud TableStore Driver for YCSB + +This driver provides a YCSB workload for Alibaba's hosted TableStore, a fully managed NoSQL cloud database service that enables storage of a massive amount of structured and semi-structured data. + +This binding is based on the Java SDK for TableStore. + +## Quick Start + +### Setup a TableStore Instance + +Login to the Alibaba Cloud Console and follow the [Create Instance](https://www.alibabacloud.com/help/doc-detail/55211.htm?spm=a2c63.p38356.b99.17.6822642crAxqTI). +Make a note of the instance name. + +### Create a Table for YCSB Testing + +Follow the [Create Table](https://www.alibabacloud.com/help/doc-detail/55212.htm?spm=a2c63.p38356.b99.18.1e4e50b9dXCcmC). + +The primary key must be exactly one column; the type is 'String'. +Make a note of the table name and primary key name. + +### `tablestore.properties` + +tablestore.properties is the file specifying information needed by TableStore binding. +The minimal tablestore.properties should be look like: + +``` +alibaba.cloud.tablestore.access_id = $access_id +alibaba.cloud.tablestore.access_key = $access_key +# the Instance Access URL in 'instance details' +alibaba.cloud.tablestore.end_point = $end_point +alibaba.cloud.tablestore.instance_name = $instance_name +alibaba.cloud.tablestore.primary_key = $primary_key +``` + +For access_id and access_key, please refer to [Java SDK: Configure an AccessKey](https://www.alibabacloud.com/help/doc-detail/43009.htm?spm=a2c63.p38356.b99.134.728966fcMpTYD1). + +### Load and Run a Workload + +```zsh +table_name='table_name' + +bin/ycsb load tablestore -P workloads/workloada -P tablestore/conf/tablestore.properties -p table=$table_name -threads 2 +bin/ycsb run tablestore -P workloads/workloada -P tablestore/conf/tablestore.properties -p table=$table_name -threads 2 +``` diff --git a/tablestore/conf/tablestore.properties b/tablestore/conf/tablestore.properties new file mode 100644 index 00000000..f0a9806b --- /dev/null +++ b/tablestore/conf/tablestore.properties @@ -0,0 +1,33 @@ +# Copyright (c) 2018 YCSB contributors. All rights reserved. + +# Licensed under the Apache License, Version 2.0 (the "License"); you +# may not use this file except in compliance with the License. You +# may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. See accompanying +# LICENSE file. + +alibaba.cloud.tablestore.access_id = +alibaba.cloud.tablestore.access_key = +alibaba.cloud.tablestore.end_point = +alibaba.cloud.tablestore.instance_name = +alibaba.cloud.tablestore.primary_key = + +# alibaba.cloud.tablestore.max_version = 1 + +# the following parameters provided by Java SDK can be tuned + +# max timeout in millisecond for open a http connection, default value in SDK is 15000 +# alibaba.cloud.tablestore.connection_timeout = + +# max timeout in millisecond for transfering data with a connection, default value in SDK is 15000 +# alibaba.cloud.tablestore.socket_timeout = + +# max http connections can be open, default value in SDK is 300 +# alibaba.cloud.tablestore.max_connections = diff --git a/tablestore/pom.xml b/tablestore/pom.xml new file mode 100755 index 00000000..4b2c7adc --- /dev/null +++ b/tablestore/pom.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.yahoo.ycsb</groupId> + <artifactId>binding-parent</artifactId> + <version>0.16.0-SNAPSHOT</version> + <relativePath>../binding-parent</relativePath> + </parent> + + <artifactId>tablestore-binding</artifactId> + <name>TableStore DB Binding</name> + + <dependencies> + <dependency> + <groupId>com.yahoo.ycsb</groupId> + <artifactId>core</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.aliyun.openservices</groupId> + <artifactId>tablestore</artifactId> + <version>${tablestore.version}</version> + </dependency> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <version>1.2.17</version> + </dependency> + </dependencies> + +</project> diff --git a/tablestore/src/main/java/com/yahoo/ycsb/db/tablestore/TableStoreClient.java b/tablestore/src/main/java/com/yahoo/ycsb/db/tablestore/TableStoreClient.java new file mode 100755 index 00000000..a635fd99 --- /dev/null +++ b/tablestore/src/main/java/com/yahoo/ycsb/db/tablestore/TableStoreClient.java @@ -0,0 +1,276 @@ +/* + * Copyright 2018 YCSB Contributors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. See accompanying + * LICENSE file. + */ + +package com.yahoo.ycsb.db.tablestore; + +import java.util.*; +import java.util.function.*; + +import com.yahoo.ycsb.*; + +import com.alicloud.openservices.tablestore.*; +import com.alicloud.openservices.tablestore.model.*; + +import org.apache.log4j.Logger; + +/** + * TableStore Client for YCSB. + */ + +public class TableStoreClient extends DB { + + private static SyncClient client; + + private int maxVersions = 1; + private String primaryKeyName; + + private static final Logger LOGGER = Logger.getLogger(TableStoreClient.class); + + // nasty here as currently there is no support of JEP218 + private void setIntegerProperty( + Properties properties, + String propertyName, + ClientConfiguration clientConfiguration, + Function<Integer, Boolean> qualifyFunction, + BiConsumer<ClientConfiguration, Integer> setFunction) throws DBException { + String propertyString = properties.getProperty(propertyName); + if (propertyString != null) { + Integer propertyInteger = new Integer(propertyString); + if (qualifyFunction.apply(propertyInteger).booleanValue()) { + setFunction.accept(clientConfiguration, propertyInteger); + } else { + String errorMessage = "Illegal argument." + propertyName + ":" + propertyString; + LOGGER.error(errorMessage); + throw new DBException(errorMessage); + } + } + } + + @Override + public void init() throws DBException { + + Properties properties = getProperties(); + String accessID = properties.getProperty("alibaba.cloud.tablestore.access_id"); + String accessKey = properties.getProperty("alibaba.cloud.tablestore.access_key"); + String endPoint = properties.getProperty("alibaba.cloud.tablestore.end_point"); + String instanceName = properties.getProperty("alibaba.cloud.tablestore.instance_name"); + String maxVersion = properties.getProperty("alibaba.cloud.tablestore.max_version", "1"); + + maxVersions = Integer.parseInt(maxVersion); + primaryKeyName = properties.getProperty("alibaba.cloud.tablestore.primary_key", ""); + + ClientConfiguration clientConfiguration = new ClientConfiguration(); + + setIntegerProperty( + properties, + "alibaba.cloud.tablestore.connection_timeout", + clientConfiguration, + (Integer t) -> t > 0, + (ClientConfiguration c, Integer t) -> c.setConnectionTimeoutInMillisecond(t.intValue())); + + setIntegerProperty( + properties, + "alibaba.cloud.tablestore.socket_timeout", + clientConfiguration, + (Integer t) -> t > 0, + (ClientConfiguration c, Integer t) -> c.setSocketTimeoutInMillisecond(t.intValue())); + + setIntegerProperty( + properties, + "alibaba.cloud.tablestore.max_connections", + clientConfiguration, + (Integer t) -> t > 0, + (ClientConfiguration c, Integer t) -> c.setMaxConnections(t.intValue())); + + try { + synchronized (TableStoreClient.class) { + if (client == null) { + client = new SyncClient(endPoint, accessID, accessKey, instanceName, clientConfiguration); + LOGGER.info("new tablestore sync client\tendpoint:" + endPoint + "\tinstanceName:" + instanceName); + } + } + } catch (IllegalArgumentException e) { + throw new DBException("Illegal argument passed in. Check the format of your parameters.", e); + } + } + + private void setResult(Set<String> fields, Map<String, ByteIterator> result, Row row) { + if (row != null) { + if (fields != null) { + for (String field : fields) { + result.put(field, new StringByteIterator((row.getColumn(field).toString()))); + } + } else { + for (Column column : row.getColumns()) { + result.put(column.getName(), new StringByteIterator(column.getValue().asString())); + } + } + } + } + + private Status dealWithTableStoreException(TableStoreException e) { + if (e.getErrorCode().contains("OTSRowOperationConflict")) { + return Status.ERROR; + } + LOGGER.error(e); + return Status.ERROR; + } + + @Override + public Status read(String table, String key, Set<String> fields, Map<String, ByteIterator> result) { + try { + // set primary key + PrimaryKeyColumn[] primaryKeyColumns = new PrimaryKeyColumn[1]; + primaryKeyColumns[0] = new PrimaryKeyColumn(primaryKeyName, PrimaryKeyValue.fromString(key)); + PrimaryKey primaryKey = new PrimaryKey(primaryKeyColumns); + // set table_name + SingleRowQueryCriteria singleRowQueryCriteria = new SingleRowQueryCriteria(table, primaryKey); + singleRowQueryCriteria.setMaxVersions(maxVersions); + // set columns + if (fields != null) { + singleRowQueryCriteria.addColumnsToGet(fields.toArray(new String[0])); + } + // set get_row request + GetRowRequest getRowRequest = new GetRowRequest(); + getRowRequest.setRowQueryCriteria(singleRowQueryCriteria); + // operate + GetRowResponse getRowResponse = client.getRow(getRowRequest); + // set the result + setResult(fields, result, getRowResponse.getRow()); + return Status.OK; + } catch (TableStoreException e) { + return dealWithTableStoreException(e); + } catch (Exception e) { + LOGGER.error(e); + return Status.ERROR; + } + } + + @Override + public Status scan(String table, String startkey, int recordcount, + Set<String> fields, Vector<HashMap<String, ByteIterator>> result) { + try { + // set primary key + PrimaryKeyColumn[] startKey = new PrimaryKeyColumn[1]; + startKey[0] = new PrimaryKeyColumn(primaryKeyName, PrimaryKeyValue.fromString(startkey)); + + PrimaryKeyColumn[] endKey = new PrimaryKeyColumn[1]; + endKey[0] = new PrimaryKeyColumn(primaryKeyName, PrimaryKeyValue.INF_MAX); + + RangeRowQueryCriteria criteria = new RangeRowQueryCriteria(table); + criteria.setInclusiveStartPrimaryKey(new PrimaryKey(startKey)); + criteria.setExclusiveEndPrimaryKey(new PrimaryKey(endKey)); + criteria.setMaxVersions(maxVersions); + // set columns + if (fields != null) { + criteria.addColumnsToGet(fields.toArray(new String[0])); + } + // set limit + criteria.setLimit(recordcount); + // set the request + GetRangeRequest getRangeRequest = new GetRangeRequest(); + getRangeRequest.setRangeRowQueryCriteria(criteria); + GetRangeResponse getRangeResponse = client.getRange(getRangeRequest); + // set the result + List<Row> rows = getRangeResponse.getRows(); + for (Row row : rows) { + HashMap<String, ByteIterator> values = new HashMap<>(); + setResult(fields, values, row); + result.add(values); + } + return Status.OK; + } catch (TableStoreException e) { + return dealWithTableStoreException(e); + } catch (Exception e) { + LOGGER.error(e); + return Status.ERROR; + } + } + + @Override + public Status update(String table, String key, + Map<String, ByteIterator> values) { + try { + PrimaryKeyColumn[] primaryKeyColumns = new PrimaryKeyColumn[1]; + primaryKeyColumns[0] = new PrimaryKeyColumn(primaryKeyName, PrimaryKeyValue.fromString(key)); + PrimaryKey primaryKey = new PrimaryKey(primaryKeyColumns); + RowUpdateChange rowUpdateChange = new RowUpdateChange(table, primaryKey); + + for (Map.Entry<String, ByteIterator> entry: values.entrySet()) { + rowUpdateChange.put(entry.getKey(), ColumnValue.fromString(entry.getValue().toString())); + } + + UpdateRowRequest updateRowRequest = new UpdateRowRequest(); + updateRowRequest.setRowChange(rowUpdateChange); + client.updateRow(updateRowRequest); + return Status.OK; + } catch (TableStoreException e) { + return dealWithTableStoreException(e); + } catch (Exception e) { + LOGGER.error(e); + return Status.ERROR; + } + } + + @Override + public Status insert(String table, String key, Map<String, ByteIterator> values) { + try { + // set the primary key + PrimaryKeyColumn[] primaryKeyColumns = new PrimaryKeyColumn[1]; + primaryKeyColumns[0] = new PrimaryKeyColumn(primaryKeyName, PrimaryKeyValue.fromString(key)); + PrimaryKey primaryKey = new PrimaryKey(primaryKeyColumns); + RowPutChange rowPutChange = new RowPutChange(table, primaryKey); + // set the columns + for (Map.Entry<String, ByteIterator> entry: values.entrySet()) { + rowPutChange.addColumn(entry.getKey(), ColumnValue.fromString(entry.getValue().toString())); + } + // set the putRow request + PutRowRequest putRowRequest = new PutRowRequest(); + putRowRequest.setRowChange(rowPutChange); + // operate + client.putRow(putRowRequest); + return Status.OK; + } catch (TableStoreException e) { + return dealWithTableStoreException(e); + } catch (Exception e) { + LOGGER.error(e); + return Status.ERROR; + } + } + + @Override + public Status delete(String table, String key) { + try { + PrimaryKeyColumn[] primaryKeyColumns = new PrimaryKeyColumn[1]; + primaryKeyColumns[0] = new PrimaryKeyColumn(primaryKeyName, PrimaryKeyValue.fromString(key)); + PrimaryKey primaryKey = new PrimaryKey(primaryKeyColumns); + + RowDeleteChange rowDeleteChange = new RowDeleteChange(table, primaryKey); + + DeleteRowRequest deleteRowRequest = new DeleteRowRequest(); + deleteRowRequest.setRowChange(rowDeleteChange); + client.deleteRow(deleteRowRequest); + return Status.OK; + } catch (TableStoreException e) { + return dealWithTableStoreException(e); + } catch (Exception e) { + LOGGER.error(e); + return Status.ERROR; + } + } + +} \ No newline at end of file diff --git a/tablestore/src/main/java/com/yahoo/ycsb/db/tablestore/package-info.java b/tablestore/src/main/java/com/yahoo/ycsb/db/tablestore/package-info.java new file mode 100644 index 00000000..381044fa --- /dev/null +++ b/tablestore/src/main/java/com/yahoo/ycsb/db/tablestore/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2018 YCSB Contributors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. See accompanying + * LICENSE file. + */ + +/** + * The YCSB binding for <a href="https://www.alibabacloud.com/product/table-store">TableStore</a>. + */ +package com.yahoo.ycsb.db.tablestore; + diff --git a/tablestore/src/main/resources/log4j.properties b/tablestore/src/main/resources/log4j.properties new file mode 100644 index 00000000..1f49c0ae --- /dev/null +++ b/tablestore/src/main/resources/log4j.properties @@ -0,0 +1,25 @@ +# Copyright (c) 2018 YCSB contributors. All rights reserved. + +# Licensed under the Apache License, Version 2.0 (the "License"); you +# may not use this file except in compliance with the License. You +# may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. See accompanying +# LICENSE file. + +#define the console appender +log4j.appender.consoleAppender = org.apache.log4j.ConsoleAppender + +# now define the layout for the appender +log4j.appender.consoleAppender.layout = org.apache.log4j.PatternLayout +log4j.appender.consoleAppender.layout.ConversionPattern=%-4r [%t] %-5p %c %x -%m%n + +# now map our console appender as a root logger, means all log messages will go +# to this appender +log4j.rootLogger = INFO, consoleAppender -- GitLab