You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache Ignite 2.17 C++客户端:SQL表与KV API数据互通及游标问题

Apache Ignite 2.17 C++瘦客户端数据写入问题

我使用Apache Ignite 2.17 C++瘦客户端写入记录,已创建关联缓存SQL_PUBLIC_NPV_RESULTS的npv_results表。最初通过SqlFieldsQuery插入数据时,SQL表可正常查询数据,但循环插入8万条记录时出现「Too many open cursors」错误;转而使用KV API的putAll进行批量插入后,缓存大小正常增长,但SQL表为空,无法通过GridGain控制台或DBeaver查询到数据。

我认为原因是KV API插入的是字符串格式的键值对,与表结构不兼容,且C++客户端无Binary Object API可注册对象模板。现需解决两个核心问题:

  1. 若退回SqlFieldsQuery,如何解决游标未自动关闭的问题?
  2. 若继续使用KV API的putAll批量插入,如何让数据同步到SQL表中?

附相关代码片段

1. SqlFieldsQuery插入代码

// 示例SqlFieldsQuery插入逻辑
IgniteClientConfiguration cfg;
cfg.SetEndpoints({"127.0.0.1:10800"});

IgniteClient client = IgniteClient::Start(cfg);
CacheClient<int, std::string> cache = client.GetOrCreateCache<int, std::string>("SQL_PUBLIC_NPV_RESULTS");

for (int i = 0; i < 80000; ++i) {
    SqlFieldsQuery query("INSERT INTO npv_results(id, value) VALUES(?, ?)");
    query.SetArgs(i, std::to_string(i * 1.5));
    cache.Query(query);
}

2. KV API putAll批量插入代码

// 示例KV API putAll批量插入逻辑
IgniteClientConfiguration cfg;
cfg.SetEndpoints({"127.0.0.1:10800"});

IgniteClient client = IgniteClient::Start(cfg);
CacheClient<std::string, std::string> cache = client.GetOrCreateCache<std::string, std::string>("SQL_PUBLIC_NPV_RESULTS");

std::map<std::string, std::string> batchData;
for (int i = 0; i < 80000; ++i) {
    batchData.insert({std::to_string(i), std::to_string(i * 1.5)});
}

cache.PutAll(batchData);

3. Java创建npv_results表的代码

// 示例Java创建表逻辑
Ignite ignite = Ignition.start();
CacheConfiguration<Integer, NpvResult> cacheCfg = new CacheConfiguration<>("SQL_PUBLIC_NPV_RESULTS");
cacheCfg.setIndexedTypes(Integer.class, NpvResult.class);

IgniteCache<Integer, NpvResult> cache = ignite.getOrCreateCache(cacheCfg);

// 执行建表SQL
cache.query(new SqlFieldsQuery(
    "CREATE TABLE npv_results (" +
        "id INT PRIMARY KEY, " +
        "value DOUBLE) " +
    "WITH \"CACHE_NAME=SQL_PUBLIC_NPV_RESULTS\""
)).getAll();

解决方案

问题1:退回SqlFieldsQuery解决游标泄漏问题

「Too many open cursors」是因为每次调用cache.Query(query)后,返回的QueryCursor未被显式关闭,导致资源泄漏。C++瘦客户端中QueryCursor是RAII对象,但循环中频繁创建未及时释放仍会累积打开的游标。

解决方法:

  • 显式关闭游标:每次调用Query后获取游标对象,使用完毕后主动关闭,或利用RAII特性确保对象生命周期结束时自动释放。
  • 批量插入优化:将单条插入改为批量SQL插入(一次插入多条数据),大幅减少游标创建次数。

示例优化后的SqlFieldsQuery批量插入代码:

IgniteClientConfiguration cfg;
cfg.SetEndpoints({"127.0.0.1:10800"});

IgniteClient client = IgniteClient::Start(cfg);
CacheClient<int, std::string> cache = client.GetOrCreateCache<int, std::string>("SQL_PUBLIC_NPV_RESULTS");

const int BATCH_SIZE = 1000;
for (int i = 0; i < 80000; i += BATCH_SIZE) {
    std::string sql = "INSERT INTO npv_results(id, value) VALUES ";
    std::vector<SqlField> args;

    for (int j = 0; j < BATCH_SIZE && (i + j) < 80000; ++j) {
        if (j > 0) sql += ", ";
        sql += "(?, ?)";
        args.push_back((i + j));
        args.push_back(std::to_string((i + j) * 1.5));
    }

    SqlFieldsQuery query(sql);
    query.SetArgs(args);
    auto cursor = cache.Query(query);
    cursor.Close(); // 显式关闭游标
}

关键说明:

  • 批量插入将1000条数据合并为一次SQL请求,大幅降低游标创建数量。
  • 显式调用cursor.Close()确保资源及时释放,避免游标泄漏。

问题2:使用KV API putAll让数据同步到SQL表

KV API插入的数据无法被SQL查询到,是因为插入的键值对格式不符合SQL表对应的缓存索引结构:SQL表关联的缓存要求键和值必须与表定义的类型匹配,且值需要是符合Ignite二进制格式的对象(而非简单字符串)。

由于C++瘦客户端没有Binary Object API,可通过以下两种方式解决:

方式1:使用缓存的键值类型与SQL表定义严格匹配

确保缓存的键类型对应SQL表的主键类型,值类型使用与表字段匹配的复合类型(如自定义结构体,实现Ignite二进制序列化)。

示例代码:

// 定义与SQL表对应的结构体
struct NpvResult {
    int id;
    double value;

    // 实现二进制序列化/反序列化逻辑
    friend BinaryWriter& operator<<(BinaryWriter& writer, const NpvResult& obj) {
        writer.WriteInt32("id", obj.id);
        writer.WriteDouble("value", obj.value);
        return writer;
    }

    friend BinaryReader& operator>>(BinaryReader& reader, NpvResult& obj) {
        obj.id = reader.ReadInt32("id");
        obj.value = reader.ReadDouble("value");
        return reader;
    }
};

// 使用匹配的缓存类型
IgniteClientConfiguration cfg;
cfg.SetEndpoints({"127.0.0.1:10800"});

IgniteClient client = IgniteClient::Start(cfg);
CacheClient<int, NpvResult> cache = client.GetOrCreateCache<int, NpvResult>("SQL_PUBLIC_NPV_RESULTS");

std::map<int, NpvResult> batchData;
for (int i = 0; i < 80000; ++i) {
    NpvResult res;
    res.id = i;
    res.value = i * 1.5;
    batchData.insert({i, res});
}

cache.PutAll(batchData);

关键说明:

  • 结构体NpvResult必须实现Ignite二进制序列化的运算符重载,确保对象能被Ignite正确解析。
  • 缓存的键类型int对应SQL表的主键id INT,值类型NpvResult对应表的字段结构,插入的数据会自动被SQL索引识别。

方式2:通过SQL语句预定义缓存的键值映射

如果无法使用结构体,可在创建SQL表时明确指定键的字段,确保KV API插入的键对应主键,值以MAP格式存储对应字段:

修改Java建表语句,指定键值类型:

cache.query(new SqlFieldsQuery(
    "CREATE TABLE npv_results (" +
        "id INT PRIMARY KEY, " +
        "value DOUBLE) " +
    "WITH \"CACHE_NAME=SQL_PUBLIC_NPV_RESULTS, KEY_TYPE=java.lang.Integer, VALUE_TYPE=java.util.Map\""
)).getAll();

然后C++端使用int作为键,std::map<std::string, IgniteVariant>作为值插入:

IgniteClientConfiguration cfg;
cfg.SetEndpoints({"127.0.0.1:10800"});

IgniteClient client = IgniteClient::Start(cfg);
CacheClient<int, std::map<std::string, IgniteVariant>> cache = client.GetOrCreateCache<int, std::map<std::string, IgniteVariant>>("SQL_PUBLIC_NPV_RESULTS");

std::map<int, std::map<std::string, IgniteVariant>> batchData;
for (int i = 0; i < 80000; ++i) {
    std::map<std::string, IgniteVariant> valueMap;
    valueMap["id"] = IgniteVariant(i);
    valueMap["value"] = IgniteVariant(static_cast<double>(i * 1.5));
    batchData.insert({i, valueMap});
}

cache.PutAll(batchData);

关键说明:

  • 建表时指定VALUE_TYPE=java.util.Map,允许值以Map格式存储字段。
  • C++端使用IgniteVariant兼容不同数据类型,确保字段类型与SQL表定义一致。

内容的提问来源于stack exchange,提问作者Haleema Khan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 17:23:20