Tablestore入门手册--表(Table)管理

表管理接口概述

API 描述
createTable 创建表
deleteTable 删除表
listTable 列出实例下的所有表
updateTable 更新表(在表被创建之后,动态的更改表的配置或预留吞吐量)
describeTable 获取表的详细信息

上述API操作是Tablestore最基础的API。官方提供了Java、Go、Node.js、Python、PHP、C#、C++语言的SDK。

createTable

创建表可以在控制台进行。在使用API进行创建的时候,需要指定表的配置信息和预留吞吐量等信息。

说明

  • 表格创建好后服务端有初始化时间,需要等待几秒钟才能对表进行读写,否则会出现异常

表限制

参数说明

创建表的参数主要包括如下几部分:

  • TableMeta: 表的结构信息,包含表的名称以及表的主键定义
  • TableOptions: 表的配置选项,用于配置TTL、MaxVersions等
  • ReservedThroughput:表的预留读写吞吐量设置

表结构 TableMeta

参数 定义 说明
TableName 表名
List 表的主键定义 1. 注意: 最多可设置 4 个主键,主键的配置及顺序一旦设置便不可修改。
2. 表格存储可包含多个主键列。主键列是有顺序的,与用户添加的顺序相同,例如, PRIMARY KEY (A, B, C) 与 PRIMARY KEY (A, C, B) 是不同的两个主键结构。表格存储会按照主键的大小为行排序,具体参见表格存储数据模型和查询操作
3. 第一列主键作为分片键。分片键相同的数据会存放在同一个分片内,所以相同分片键下最好不要超过 10 G 以上数据,否则会导致单分片过大,无法分裂。另外,数据的读/写访问最好在不同的分片键上均匀分布,有利于负载均衡。
4. 属性列不需要定义。表格存储每行的数据列都可以不同,属性列的列名在写入时指定。

表配置 TableOptions

Tablestore入门手册--表(Table)管理

预留吞吐量 ReservedThroughtput

  • ReservedThroughtput表的预留读/写吞吐量配置。

    • 设置 ReservedThroughtput 后,表格存储按照您预留读/写吞吐量进行计费。
    • 当 ReservedThroughtput 大于 0 时,表格存储会按照预留量和持续时间进行计费,超出预留的部分进行按量计费。更多信息参见计费,以免产生未期望的费用。
    • 默认值为 0,即完全按量计费。
    • 容量型实例的预留读/写吞吐量只能设置为 0,不允许预留。

Java代码示例

// 创建普通表(不使用索引等功能)
public void createTable() {
    TableMeta tableMeta = new TableMeta(TABLE_NAME);
    // 为主表添加主键列。
    tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_1", PrimaryKeyType.STRING));
    tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_2", PrimaryKeyType.INTEGER));
    tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_3", PrimaryKeyType.BINARY));
    // 设置该主键为自增列
    tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_4", PrimaryKeyType.INTEGER, PrimaryKeyOption.AUTO_INCREMENT));

    // 数据的过期时间,单位秒, -1代表永不过期,例如设置过期时间为一年, 即为 365 * 24 * 3600。
    int timeToLive = -1;
    // 保存的最大版本数,设置为3即代表每列上最多保存3个最新的版本。(如果使用索引,maxVersions只能等于1)
    int maxVersions = 3;

    TableOptions tableOptions = new TableOptions(timeToLive, maxVersions);
    CreateTableRequest request = new CreateTableRequest(tableMeta, tableOptions);
    // 设置读写预留值,容量型实例只能设置为0,高性能实例可以设置为非零值。
    request.setReservedThroughput(new ReservedThroughput(new CapacityUnit(0, 0)));
    client.createTable(request);
}

表设计相关技巧

分区键选择

表格存储Tablestore第一个主键是分区键,决定了数据分布式存储的位置。分区键的选择决定了数据是否均匀散列在不同的后端服务器上。散列是分布式数据库中常见的问题之一,数据的散列能够避免热点问题。因此在选择主键的时候,首先要选好分区键。其中比较常用的两种方式如下:

  1. 分区键可以选择业务上比较分散的Key放到第一列,如userID、DeviceId等。如果每个用户数据分布严重不均匀,则需要另外选择其它字段。
  2. 如果分区键不好设计,可以对想要当做分区键的值拼接MD5,这样能够保证数据散列。

更多的设计技巧见两篇表设计实践。 表设计的最佳实践表设计实践

数据生命周期TTL设计

数据生命周期(Time To Live,简称 TTL)是数据表的一个特性,即数据的存活时间,单位为秒。表格存储会在后台对超过存活时间的数据进行清理,以减少用户的数据存储空间,降低存储成本。

TTL特性能够实现数据过期自动删除,因此在很多场景能够用到,这些场景中随着时间的流逝数据的价值会降低,特别适合TTL。

  1. 舆情监控。如果用户只关心最近3个月的信息,因此超过3个月的数据可以自动删除。
  2. 物流轨迹。物流轨迹的记录信息在用户收到货物后就失去了其大部分的价值,因此可以设置1个月的TTL,来节省大量的存储费用。
  3. 系统日志。系统的运行日志大多数只有在系统出问题时候才会查看,而随着时间流逝,很早之前的系统日志也就失去了存在的价值,而这部分数据存储在数据库中会占用存储成本,设置合适的TTL能够自动将之前的数据删除。

TTL暂时仅支持主表,索引等还暂时不支持TTL特性。因此如果使用了二级索引或者多元索引,需要主表的TTL=-1,即永久不会失效,对于已经创建好表的用户,可以通过 UpdateTable 接口动态更改主表的 TTL。

Java 示例代码

public void updateTTL() {
        UpdateTableRequest request = new UpdateTableRequest(TABLE_NAME);
        int ttl = -1;
        request.setTableOptionsForUpdate(new TableOptions(ttl));
        client.updateTable(request);
 }

主键自增

主键列自增功能是指若用户指定某一列主键为自增列,在其写入数据时,表格存储会自动为用户在这一列产生一个新的值,且这个值为同一个分区键下该列的最大值。

主键自增列的功能特性主要有生成数字ID且ID严格递增保证顺序,这个特性决定了许多场景能够使用。

  • 如电商网站的商品 ID、大型网站的用户 ID等,这些场景中数值越大,表示该商品、用户越新。
  • 如论坛帖子的 ID、聊天工具的消息 ID等消息保序的场景,这些场景需要严格保证消息递增,不然用户读取到的顺序就会乱序。举个例子,假如用户发朋友圈场景,用户在1点5分11.1秒时刻发送了一个朋友圈记录a,另一个用户在1点5分11.2秒发送了一个朋友圈记录b,则需要严格保证记录b的id比记录a的id大。主键自增在聊天系统IM中的应用见 Table Store主键列自增功能在IM系统中的应用

主键自增的一些限制:

  • 表格存储支持多个主键,第一个主键为分区键,分区键不允许设置为自增列,其它任一主键都可以设置为自增列。
  • 每张表最多只允许设置一个主键为自增列。
  • 属性列不能设置为自增列。
  • 仅支持在创建表的时候指定自增列,对于已存在的表不支持创建自增列。

Java示例代码

// 主键自增 创建主表
public void createTableWithPKAutoIncrement() {
    TableMeta tableMeta = new TableMeta(TABLE_NAME + "AUTO_INCREMENT");
    tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_1", PrimaryKeyType.STRING));
    // 设置该主键为自增列
    tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_autoI_increment", PrimaryKeyType.INTEGER, PrimaryKeyOption.AUTO_INCREMENT));
    tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_3", PrimaryKeyType.INTEGER));

    TableOptions tableOptions = new TableOptions(-1, 1);
    CreateTableRequest request = new CreateTableRequest(tableMeta, tableOptions);
    client.createTable(request);
}

// 自增列的数据插入
public void putRow(){
    PrimaryKey primaryKey = PrimaryKeyBuilder.createPrimaryKeyBuilder()
        .addPrimaryKeyColumn("pk_1", PrimaryKeyValue.fromString("test1"))
        .addPrimaryKeyColumn("pk_auto_increment", PrimaryKeyValue.AUTO_INCREMENT)
        .addPrimaryKeyColumn("pk_3", PrimaryKeyValue.fromLong(100))
        .build();
    RowPutChange rowPutChange = new RowPutChange(TABLE_NAME , primaryKey);
    rowPutChange.addColumn("attr_1", ColumnValue.fromLong(100));
    PutRowRequest request = new PutRowRequest(rowPutChange);
    client.putRow(request);
}

deleteTable

删除表,只需要指定表名字即可。

说明

  • 如果表中创建了多元索引,需要先删除多元索引才可以删除表。

Java代码示例


    public void deleteTable() {
        DeleteTableRequest request = new DeleteTableRequest(TABLE_NAME);
        client.deleteTable(request);
    }

listTable

该API能够列出当前实例下已创建的所有表的表名。

Java代码示例


    public void listTable() {
        ListTableResponse response = client.listTable();
        System.out.println("表的列表如下:");
        for (String tableName : response.getTableNames()) {
            System.out.println(tableName);
        }
    }    

updateTable

该API能够动态的更改表的配置或预留吞吐量。可以只修改配置或只修改预留吞吐量,也可以一起修改。

说明

  • 该API调用频率有限制,为每 2分钟1次。

Java代码示例


    public void updateTable() {
        UpdateTableRequest request = new UpdateTableRequest(TABLE_NAME);
        // 修改预留吞吐
        request.setReservedThroughputForUpdate(new ReservedThroughput(new CapacityUnit(0, 0)));
        // 修改表的最大保留版本、TTL等
        request.setTableOptionsForUpdate(new TableOptions(-1, 1));
        client.updateTable(request);
    }

describeTable

该API可以获得表的结构信息(TableMeta)、配置信息(TableOptions)和预留读/写吞吐量的情(ReservedThroughputDetails,包括调整时间)、表分区信息等, 如图所示。

Tablestore入门手册--表(Table)管理

Java代码示例

public void describeTable() {
        DescribeTableRequest request = new DescribeTableRequest(TABLE_NAME);
        DescribeTableResponse response = client.describeTable(request);
        TableMeta tableMeta = response.getTableMeta();
        System.out.println("表的名称:" + tableMeta.getTableName());

        System.out.println("表的主键:");
        for (PrimaryKeySchema schema : tableMeta.getPrimaryKeyList()) {
            System.out.println("\t主键名字:" + schema.getName() + "\t主键类型:" + schema.getType()
                + "\t自增列:" + (schema.getOption() == null ? "false" : schema.getOption().equals(PrimaryKeyOption.AUTO_INCREMENT)));
        }

        System.out.println("预定义列信息:");
        for (DefinedColumnSchema schema : tableMeta.getDefinedColumnsList()) {
            System.out.println("\t主键名字:" + schema.getName() + "\t主键类型:" + schema.getType());
        }

        System.out.println("二级索引信息:");
        for (IndexMeta meta : response.getIndexMeta()) {
            System.out.println("\t索引名字:" + meta.getIndexName());
            System.out.println("\t\t索引类型:" + meta.getIndexType());
            System.out.println("\t\t索引主键:" + meta.getPrimaryKeyList());
            System.out.println("\t\t索引预定义列:" + meta.getDefinedColumnsList());
        }

        TableOptions tableOptions = response.getTableOptions();
        System.out.println("TableOptions:");
        System.out.println("\t表的TTL:" + tableOptions.getTimeToLive());
        System.out.println("\t表的MaxVersions:" + tableOptions.getMaxVersions());

        ReservedThroughputDetails rtd = response.getReservedThroughputDetails();
        System.out.println("预留吞吐量:");
        System.out.println("\t读:" + rtd.getCapacityUnit().getReadCapacityUnit());
        System.out.println("\t写:" + rtd.getCapacityUnit().getWriteCapacityUnit());
        System.out.println("\t最近上调时间: " + new Date(rtd.getLastIncreaseTime() * 1000));
        System.out.println("\t最近下调时间: " + new Date(rtd.getLastDecreaseTime() * 1000));

        List<PrimaryKey> shardSplits = response.getShardSplits();
        System.out.println("表分区信息:");
        for (PrimaryKey primaryKey : shardSplits) {
            System.out.println("\t分裂点信息: " + primaryKey.getPrimaryKeyColumnsMap());
        }
    }

专家服务

如有疑问或者需要更好的在线支持,欢迎加入钉钉群:“表格存储公开交流群”(群号:23307953)。群内提供免费的在线专家服务,欢迎扫码加入。

Tablestore入门手册--表(Table)管理

上一篇:3分钟短文:说说Laravel通用缓存Cache的使用技巧


下一篇:Tablestore的SDK如何配置自定义重试