TDR表 string 类型多语言兼容性

TDR 表定义中的 string 类型字段,在底层存储时约定以 \0 结尾(C 风格字符串)。 不同编程语言的 SDK 对 \0 的处理方式并不完全一致,当业务同时使用多种语言读写同一张表时(特别是 key 字段),需要特别注意,避免出现同一逻辑字符串被解读为不同二进制内容的兼容性问题。

差异产生的原因:TDR 协议本身源自 C,而 C 语言没有独立的 string 类型,字符串以 const char* + \0 终止符的形式表达,长度信息隐含在 \0 中;而 Go、Java 都有原生的 string 类型(其长度独立存储,字符串中可合法包含 \0,也可不以 \0 结尾)。因此各 SDK 在把语言原生类型转换为 TDR string 字段(C 风格字符串)时,对 \0 的补齐策略和校验程度不一致,这就是下面行为差异的根源。

1. 各 SDK 对 string 字段的处理行为

1.1 C++ SDK

接口:SetKeyStr / SetValueStr / GetKeyStr / GetValueStr / SetData / GetData

  • 入参为 C 字符串(const char*),SDK 内部按 strlen(field_value) + 1 计算长度进行打包。其实现形如:

    int TcaplusServiceRecord::SetKeyStr(IN const char* field_name, IN const char* field_value)
    {
        return SetKey(field_name, field_value, strlen(field_value) + 1);
    }
    
  • 这意味着:

    • SDK 总是打包到第一个 \0 为止,并把这个 \0 一并写入,最终存储的 string 末尾有且只有一个 \0
    • 用户传入 "abc"(无 \0)时,实际存储为 4 字节:a b c \0
    • 用户传入 "abc\0"(末尾带 \0)时,strlen 仍返回 3,实际存储同样为 4 字节 a b c \0,不会出现"末尾多个 \0";
    • 用户传入 "abc\0def"(字符串中间含 \0)时,strlen 返回 3,实际存储只有 a b c \0,后面的 "def" 会被截断丢弃
  • 读取侧 GetKeyStr / GetValueStr 返回的字符串以单个 \0 结尾,可作为标准 C 字符串使用。

1.2 Go SDK

接口:TDR 打包相关的 SetKeyStr / SetValueStr 等(以及 SetData 中 string 类型字段)

  • 在 TDR 打包时,Go SDK 会无条件string 类型字段的末尾追加一个 \0不校验用户传入的字符串末尾是否已经有 \0
  • 使用建议:在 Go SDK 中按 Go 原生 string 使用即可,不要自行在末尾追加 \0。若用户自行在字符串末尾加上 \0,SDK 仍会再追加一个 \0,最终存储结果会出现末尾多个 \0 的情况,导致与其他语言写入的数据不一致。

例如:Go 侧传入 "abc\x00",实际打包后为 "abc\x00\x00";而 C++ 侧对于同样语义的 "abc" 会打包为 "abc\x00"。二者虽然在部分场景下可被解读为同一字符串,但在作为 key 进行精确匹配时,会被视为不同的 key。

1.3 C++ / Go SDK 的 Blob 接口

接口:SetKeyBlob / SetValueBlob

  • 入参是字节数组 + 数组长度,SDK 将其视为透明的二进制数据
    • 不会校验用户输入是否以 \0 结尾;
    • 不会自动追加 \0
  • 如果业务希望与 SetKeyStr / SetValueStr 写入的数据保持二进制一致(以便跨语言 / 跨接口互读),需要用户自行在字节数组末尾追加 \0,并把长度计入 \0

场景示例:若同一个 string 字段,一端用 SetKeyStr("abc") 写入(实际存储 4 字节 a b c \0),另一端用 SetKeyBlob 读/写,必须按 4 字节处理;若另一端用 SetKeyBlob("abc", 3) 写入,则只存储 3 字节,与前者不一致,会导致 key 精确匹配失败。

1.4 Java SDK

接口:SetKeyString / SetValueString

  • 对于 string 类型字段,Tcaplus Java SDK 不会校验、也不会主动保证用户输入的字符串是否以 \0 结尾。
  • 这意味着:
    • 如果用户传入的字符串不含 \0,最终存储不会有结尾 \0
    • 如果用户自行在字符串末尾附加了 \0,最终存储会包含该 \0
  • 因此,使用 Java SDK 写入的 string 字段,其二进制形态由用户代码决定,而 C++ / Go SDK 写入的 string 字段末尾固定有且仅有一个 \0

2. 行为对比

SDK / 接口 末尾 \0 处理 是否一定有 \0 说明
C++ SetKeyStr / SetValueStr / SetData strlen+1 打包,截断至第一个 \0 并保留该 \0 是,且有且只有一个 用户无需关心 \0;字符串中间若含 \0,其后内容会被截断丢弃
Go SetKeyStr / SetValueStr / SetData SDK 自动追加(不校验) 是,至少一个;若用户自带 \0,会出现多个 按 Go 原生 string 使用,不要自行加 \0
C++ / Go SetKeyBlob / SetValueBlob SDK 不做任何处理 取决于用户 如需与 Str 接口一致,请自行追加 \0 并把长度 +1
Java SetKeyString / SetValueString SDK 不做校验与保证 取决于用户 如需与 C++ / Go 保持二进制一致,业务需自行在末尾附加一个 \0

3. 注意事项与建议

  • 跨语言访问同一张表时,string 类型 key 字段尤其容易出现兼容性问题。key 的二进制在存储层需完全一致,才能命中同一条记录。
  • 推荐做法(确保跨语言二进制一致,存储形式统一为"有效字符 + 一个 \0"):
    1. C++:使用 SetKeyStr / SetValueStr / SetData,按普通 C 字符串传入即可,SDK 会自动补上末尾 \0用户无需额外处理
    2. Go:使用 TDR 打包相关的 string 接口,直接按 Go 原生 string 传入,SDK 会自动追加 \0用户不要在末尾手动添加 \0,否则会出现"末尾多个 \0"导致与 C++ 不一致。
    3. Java:使用 SetKeyString / SetValueString 时,SDK 不会主动追加 \0。如需与 C++ / Go 的 Str 接口在存储层二进制一致,业务需自行在字符串末尾显式附加一个 \0(例如 value + "\0"),否则存储字节数会比 C++/Go 少一字节 \0,在精确匹配场景下会命中不到同一条记录。
    4. Blob 接口(C++/Go 的 SetKeyBlob / SetValueBlob):SDK 不做任何 \0 处理。若需与 Str 接口互通,请自行在字节数组末尾加上 \0,并把长度 +1 使其包含该字节。
  • 字段值为 string 类型时,写入端的约定需与读取端一致,否则在精确匹配(key 查询、条件过滤等)场景下可能出现读不到记录的问题。

results matching ""

    No results matching ""