ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ShardingSphere 5.4.1 适配人大金仓 8.6 的踩坑全记录

ShardingSphere 5.4.1 适配人大金仓 8.6 的踩坑全记录 这几个月一直在弄国产数据库环境的项目落地最绕不开的一件事就是把 ShardingSphere5.4.1 接到人大金仓 8.6 上。老实讲刚开始我以为这活儿不复杂毕竟 ShardingSphere 从 5.3.x 开始就在 DatabaseType 里加了 KingbaseES官方文档也写了支持金仓可真正把驱动换掉、配置改完之后才发现“支持”两个字背后全是细节元数据加载失败、分页数据重复、加密字段查不到、Oracle 遗留语法解析报错一个接一个。这篇文章就是我来来回回踩坑之后的完整记录适合正在做金仓适配或者准备把分库分表中间件切到国产库的团队参考。1. 适配前的两个疑问金仓到底兼容什么ShardingSphere 又认什么1.1 先摸清金仓 8.6 的“身份”很多人在适配前根本没把数据库环境理清楚上来就改配置结果浪费大量时间。金仓 8.6 不是一个单一形态的数据库它有多种兼容模式最常见的是 PGPostgreSQL兼容模式和 ORAOracle兼容模式部分版本还支持 MySQL 兼容模式。这个差异直接决定了 ShardingSphere 用什么方言解析你的 SQL。我这次适配的环境是在 PG 兼容模式下运行的这一点很关键。你可以通过下面的 SQL 确认当前兼容模式SHOW database_mode;如果返回的是pg那么后续的 SQL 解析、元数据加载都会按 PostgreSQL 的规则走如果返回ora那你得额外小心因为 ShardingSphere 的解析器可不会因为金仓开了 Oracle 兼容模式就自动变成 Oracle 方言。另外还要确认几个基础信息默认端口金仓 8.6 默认端口一般是54321而不是 PostgreSQL 的 5432。驱动类名com.kingbase8.Driver。JDBC URL 前缀jdbc:kingbase8://。默认 schemaPG 模式下通常是publicORA 模式下可能是sys或用户名同名 schema。这些信息决定了你接下来的数据源配置长什么样。我见过不少同事直接把jdbc:postgresql://改成jdbc:kingbase8://、驱动换成金仓驱动就以为完事了结果一启动就报元数据加载失败根因往往是 schema 没对上。1.2 ShardingSphere 是怎么识别数据库类型的ShardingSphere 5.x 使用 SPI 机制管理数据库类型识别方式主要有两条路径一是根据 JDBC URL 前缀判断二是通过Connection.getMetaData().getDatabaseProductName()拿到的产品名匹配。在 5.4.1 中金仓对应的是内置的KingbaseES类型。日志里一般会输出类似Database type: KingbaseES如果驱动版本比较老getDatabaseProductName()返回的可能是PostgreSQL此时 ShardingSphere 会按 PostgreSQL 方言处理。一般也能用因为在 PG 兼容模式下两者的 SQL 语法高度一致但如果你在 ORA 模式下跑又不小心写了 Oracle 特有语法解析阶段就会出问题。还有一个隐藏坑如果驱动返回的产品名在 ShardingSphere 的注册表里找不到启动时就会直接抛Cannot get database type from registry: unknown database type这种报错需要用代码手动指定 DatabaseType下面第三章会专门讲。1.3 适配前的版本与功能清单大版本确认好之后我建议把适配范围圈清楚不是所有 ShardingSphere 特性都要一步到位。这次我划定的范围是功能是否适配备注单库分表是最核心场景读写分离是与分片共存数据加密是逻辑列与密文列映射分布式主键是用 SNOWFLAKE不走数据库序列分布式事务 XA否先本地事务兜底ShardingSphere-Proxy否前端协议兼容性风险大见第六章版本上我用的是 ShardingSphere 5.4.1、金仓 V8.6、JDK 8、HikariCP 连接池。如果你用 JDK 17 也没问题但 ShardingSphere 5.4.1 在 JDK 8 上最稳。先把最小功能集跑通再逐步放开排查问题会容易很多。2. 驱动替换和连接串配置看似一分钟实际坑不少2.1 金仓驱动不在 Maven 中央仓库得手动装金仓的 JDBC 驱动kingbase8不会出现在 Maven 中央仓库里我第一次直接用仓库坐标拉取就失败了。它通常在金仓安装目录的Interface/JDBC下文件名类似kingbase8-8.6.0.jar。你需要把它手动安装到本地仓库mvn install:install-file \ -Dfilekingbase8-8.6.0.jar \ -DgroupIdcn.com.kingbase \ -DartifactIdkingbase8 \ -Dversion8.6.0 \ -Dpackagingjar装完之后在pom.xml里引入dependency groupIdcn.com.kingbase/groupId artifactIdkingbase8/artifactId version8.6.0/version /dependency有些项目不方便装本地依赖也可以直接把 jar 放到lib目录用系统依赖方式引入。总之要先确认驱动真正打进了运行环境因为后面所有连接池初化化失败一半以上都是这个原因。2.2 数据源 YAML 配置实例ShardingSphere 5.4.1 用 YAML 配置数据源时我建议坚持用 HikariCP 作为连接池。下面这份配置是经过实测可用的dataSources: ds_0: dataSourceClassName: com.zaxxer.hikari.HikariDataSource driverClassName: com.kingbase8.Driver jdbcUrl: jdbc:kingbase8://127.0.0.1:54321/demo?currentSchemapublicuseUnicodetruecharacterEncodingUTF-8 username: system password: xxxxxx maximumPoolSize: 50 minimumIdle: 5 connectionTimeout: 30000 idleTimeout: 600000 rules: - !SHARDING tables: t_order: actualDataNodes: ds_0.t_order_$-{0..3} tableStrategy: standard: shardingColumn: order_id shardingAlgorithmName: t_order_inline keyGenerateStrategy: column: order_id keyGeneratorName: snowflake shardingAlgorithms: t_order_inline: type: INLINE props: algorithm-expression: t_order_$-{order_id % 4} keyGenerators: snowflake: type: SNOWFLAKE props: worker-id: 1 props: sql-show: true有几个细节要注意driverClassName一定要写全有些版本的 HikariCP 可以根据 URL 自动推断但在金仓驱动上偶尔推断不出来宁可显式写上。currentSchemapublic这个参数建议无条件加上它能避免很多元数据加载和 schema 找不到的诡异问题。sql-show: true必须开排查问题全靠它看逻辑 SQL 和真实下发 SQL。2.3 连接池初始化失败的几个典型报错这部分我直接给出报错和排查方向帮你少走弯路报错信息实际原因处理方式Failed to load driver class com.kingbase8.Driver驱动 jar 没打进去检查依赖和打包产物确认 lib 里有驱动Connection is not available, request timed out连接串配错或端口不通telnet 127.0.0.1 54321先测连通性The connection attempt failedURL 或用户密码问题先用 DBeaver 等工具直接连排除库端问题Cannot load driver class: com.kingbase.Driver驱动类名写错确认是com.kingbase8.Driver不是com.kingbase.Driver从实践来看连接池这层问题最不值得花时间优先用客户端工具证明“JDBC 能连通”再回头检查 ShardingSphere 配置问题一下子就定位了。3. 元数据加载失败ShardingSphere 启动阶段的头号拦路虎3.1 元数据加载在启动时到底做了什么ShardingSphere 5.x 在初始化分片、加密等规则时会主动加载真实表的元数据包括表结构、列名、主键、索引等。它通过 JDBC 的DatabaseMetaData接口实现也就是说需要执行一系列基于information_schema和pg_catalog的查询。金仓 8.6 在 PG 兼容模式下information_schema和pg_catalog基本是兼容的大部分查询能正常返回。但有一个前置条件连接的用户必须有权限访问目标 schema且currentSchema指向正确。如果currentSchema没配而 ShardingSphere 又拿不到默认 schema它可能会遍历所有 schema 去找表轻则加载慢重则看到重复表名直接报冲突。3.2 schema 与 currentSchema 的真实坑我这次遇到的现象是启动日志里能看到加载数据源成功但随后报Table t_order does not exist in database demo诡异的是表明明建了。后面查下来问题出在Connection.getSchema()返回的是nullShardingSphere 无法确认表所在的 schema最终在元数据加载阶段把表过滤掉了。解决方式就是在 JDBC URL 上显式指定 schemajdbc:kingbase8://127.0.0.1:54321/demo?currentSchemapublic如果你的表建在sys或app等下就把public替换成对应 schema。这一步对于金仓 8.6 来说非常关键建议直接当标配写进连接串。另外还有一个不容易注意的点金仓某些版本的主键元数据需要通过pg_index获取如果业务表建成了无主键堆表ShardingSphere 加载到的主键信息就是空的。这会影响后续基于主键的 SQL 改写和去重逻辑。适配前最好给业务表补上主键哪怕是用雪花算法生成的分布式主键也需要在真实表上建立物理主键约束。3.3 手动指定 DatabaseType 的兜底办法正常情况下ShardingSphere 5.4.1 能通过驱动自动识别金仓为KingbaseES日志里也会打印对应的数据库类型。但如果你用的是旧版金仓驱动或者驱动对getDatabaseProductName()的实现有偏差识别就可能失败。手动指定数据库类型的做法是用ShardingSphereDataSourceFactory.createDataSource重载方法在代码里显式传入DatabaseTypeDataSource dataSource ShardingSphereDataSourceFactory.createDataSource( dataSourceMap, ruleConfigs, new Properties(), DatabaseTypeEngine.getDatabaseType(KingbaseES));这里有一点需要提醒5.4.1 源码里DatabaseTypeEngine的包名在不同小版本中可能有调整具体类路径以你本地 jar 为准。不同版本的字符串值也可能是KINGBASE或KingbaseES建议写个小的单元测试先打出来确认避免编译过了运行时报No such database type。如果是在纯 YAML 环境下没法改 Java 代码那就优先升级驱动的兼容版本让自动识别正常工作。手动指定是兜底不该是首选。4. SQL 解析与改写分页、分片键和加密下推的实测记录4.1 解析层为什么 PG 解析器能顶半个金仓ShardingSphere 5.4.1 的 SQL 解析是分层的先用对应方言的解析器把 SQL 解析成 AST再统一标准化成内部逻辑 SQL最后根据分片、加密等规则改写。金仓在 PG 兼容模式下CRUD 语法与 PostgreSQL 高度一致所以 ShardingSphere 内置的 PostgreSQL 方言解析器几乎可以“白嫖”。实际适配中大量问题不出在解析层而出在业务 SQL 里残留的 Oracle 风格语法。最典型的就是ROWNUM。比如下面这条 SQL在金仓 ORA 模式下能执行SELECT * FROM t_order WHERE ROWNUM 10;但在 ShardingSphere 里走到 PostgreSQL 方言解析器时会直接报SQL parse error. You may have an error in this SQL statement: non support token ROWNUM类似的还有()外连接、START WITH ... CONNECT BY层次查询、SYSDATE等。凡是这类 Oracle 专属写法在金仓适配 ShardingSphere 的场景下统统不建议留。处理方式很简单统一改成 PG 风格SELECT * FROM t_order LIMIT 10;我建议在适配阶段做一轮 SQL 合规扫描把所有ROWNUM、NVL、SYSDATE等 Oracle 痕迹清掉省得后面上线时被用户的一条临时查询打挂。4.2 分页改写LIMIT 与 FETCH FIRST 的兼容细节分页是分片场景下改写最频繁的逻辑。ShardingSphere 会对分页 SQL 做改写必要时还要在内存里做二次归并。比如你要查第 2 页、每页 20 条实际下发到每个分片的 SQL 会变成取前 40 条再归并截断。这种机制对金仓本身是透明的因为 PG 模式支持标准的LIMIT ? OFFSET ?语法PreparedStatement 的参数绑定也正常。但有两个细节值得注意如果业务 SQL 用了FETCH FIRST ? ROWS ONLY这种写法需要确认金仓驱动支持预编译参数绑定。实测中金仓 8.6 对FETCH FIRST的动态参数支持偶尔会有兼容问题报错类似于syntax error at or near ?。稳妥起见统一改写成LIMIT ? OFFSET ?。分片键上的分页查询如果ORDER BY字段不是分片键ShardingSphere 会把排序下推到每个分片执行再内存归并。这个过程中如果ORDER BY字段本身是加密逻辑列改写后的真实列名可能跟原始排序条件对不上出现“列不存在”的报错。处理办法是让加密字段只作为数据存储列不作为排序和查询条件列使用。4.3 加密字段下推列名大小写是金仓最容易踩的坑数据加密是 ShardingSphere 的高频功能。配置上你需要定义逻辑列、密文列、明文列和加密算法。下面是我实际用过的最小配置- !ENCRYPT encryptors: aes_encryptor: type: AES props: aes-key-value: 1234567890abcdef tables: t_user: columns: phone: cipherColumn: phone_cipher plainColumn: phone_plain encryptorName: aes_encryptor写入时业务 SQL 只需要关心逻辑列phoneShardingSphere 会自动改写为INSERT INTO t_user (phone_cipher, phone_plain)并把明文加密后写入密文列。这个机制本身不区分数据库品牌但在金仓上我遇到一个很实际的问题大小写敏感。PG 兼容模式对未加引号的标识符一律折叠为小写。如果你的建表 DDL 写的是CREATE TABLE t_user (Phone_Cipher VARCHAR(100), Phone_Plain VARCHAR(100));那么 ShardingSphere 改写出来的列名是phone_cipher双双对不上报错column phone_cipher does not exist解决办法很粗暴建表时所有表名、列名一律小写不要加双引号。如果你被历史遗留的“驼峰加引号”表结构绑住那只能在加密规则里把cipherColumn配成和物理列完全一致的大小写并在 SQL 里也带引号访问但这种做法的维护成本很高不推荐。4.4 分片键路由的非分片键查询代价分片场景下还有一个容易忽略的问题如果你按order_id分片但查询条件只用user_idShardingSphere 会把这个查询广播到所有分片再合并结果。这个过程对金仓是透明的但性能会随分片数量线性下降。我建议在 SHARDING 规则里配置绑定表bindingTables: - t_order,t_order_item哪怕是一对多的关联查询只要分片键一致就可以避免笛卡尔积式的跨分片关联。这个跟数据库品牌无关但适配金仓时尤其值得做因为金仓在并发处理上不如 PostgreSQL 原生环境宽裕少一些广播查询压力能小不少。5. 一次分页查询错乱问题的完整排查链路5.1 现象第一页正常第二页出现第一页的数据适配过程中我遇到一个很经典的坑值得单独写一节。现象是t_order按order_id % 4分片业务查询时第一页数据正常翻到第二页时出现了第一页已经出现过的数据。第一反应是分页改写参数错了但连着检查好几遍 SQL都没发现逻辑问题。后来把所有相关日志打开才发现问题不在 ShardingSphere而在金仓驱动的连接参数。5.2 排查过程从 SQL 日志到 JDBC 驱动参数第一步打开 ShardingSphere 的 SQL 日志。配置props: sql-show: true日志里会输出Logic SQL和Actual SQL。我看到逻辑 SQL 是SELECT * FROM t_order ORDER BY create_time LIMIT 20 OFFSET 20;而实际下发的 SQL 也是SELECT * FROM t_order_0 ORDER BY create_time LIMIT 20 OFFSET 20; SELECT * FROM t_order_1 ORDER BY create_time LIMIT 20 OFFSET 20;参数完全正确所以第一层排除 ShardingSphere 改写问题。第二步检查金仓是否真的按OFFSET 20返回。我用金仓客户端手工执行同样的 SQL结果也正确。于是问题收敛到 JDBC 驱动层。第三步排查连接串参数。我发现初始化时为了优化大结果集连接串里加了useCursorFetchtruedefaultRowFetchSize100这个参数在 PostgreSQL JDBC 里用于开启游标分批取数。PG JDBC 在使用游标模式下如果事务处于自动提交setFetchSize不会生效但金仓驱动的部分版本对这种组合的处理有瑕疵会导致分页查询在内存和游标之间串页最终出现翻页重复。5.3 根因与修复方案根因就是useCursorFetchtrue和 ShardingSphere 分页归并机制叠加后游标取数把“偏移量”重复计算了。修复方案很简单金仓 8.6 环境下去掉useCursorFetch和defaultRowFetchSize让分页数据一次性进入 ShardingSphere 内存归并。修改后的连接串jdbc:kingbase8://127.0.0.1:54321/demo?currentSchemapublicuseUnicodetruecharacterEncodingUTF-8改完之后连续翻页 30 轮没有重复也没有漏行。这个坑给我一个教训从 PostgreSQL 迁移到金仓时连接串参数不能原样照搬。金仓驱动虽然内核沿袭了 PG但游标取数这类行为差异很大最好在生产压测前逐项验证。5.4 排查链路的通用价值这套排查链路其实适用于所有“ShardingSphere 国产数据库”的诡异查询问题先看Logic SQL和Actual SQL确认中间件改写是否正确。再用数据库客户端手工执行Actual SQL确认数据库本身返回是否正确。最后检查 JDBC 连接参数和驱动版本重点怀疑与 fetchSize、游标、事务隔离相关的参数。按这个顺序走大部分问题能在半小时内定位。反过来如果你一上来就怀疑 ShardingSphere 的 SQL 改写很容易陷入反复翻源码的泥潭浪费时间。6. 事务模式和 Proxy 选型适配方案的边界在哪儿6.1 本地事务与 XA 在金仓上的表现ShardingSphere 5.4.1 支持三种事务模式本地事务、XA 事务和 BASE 事务。本地事务是最稳的选择。金仓 8.6 在 PG 兼容模式下单库单事务完全没问题ShardingSphere 的多分片写入在本地事务下会根据路由结果顺序提交只要业务对一致性要求不跨分片这个模式完全够用。XA 事务理论上支持但要满足两个前提一是金仓驱动实现了javax.sql.XADataSource接口二是应用里引入 Atomikos 或 Narayana 的事务管理器。我在测试中发现金仓 8.6 驱动能做 XA 注册但和 Atomikos 的日志目录权限、连接池代理对象之间存在较多组合问题。如果你对金仓 XA 不是特别熟我更建议用 Seata 这类 BASE 方案或者干脆在业务层做本地消息表补偿别把分布式事务这个重担压给数据库适配层。如果你确实要用 XA测试时可以从简单场景开始TransactionTypeHolder.set(TransactionType.XA);然后只做“两个分片同时写入”的冒烟测试观察事务管理器日志里是否出现XAER_RMERR或XAER_RMFAIL。出现这类错误基本可以确认驱动和事务管理器不兼容趁早换方案。6.2 ShardingSphere-JDBC 与 Proxy 的现实选择关于 ShardingSphere-Proxy这里要给一个比较直接的建议在人大金仓 8.6 的适配初期优先选 ShardingSphere-JDBC 嵌入式模式不要一上来就上 Proxy。原因在于 Proxy 是一个独立数据库中间件进程客户端需要以对应的数据库协议连接它。ShardingSphere-Proxy 5.4.1 对外主要支持 MySQL 和 PostgreSQL 协议金仓的自有客户端协议并非标准 PG wire protocol。如果金仓侧没有开启 PG 兼容协议端口你的应用直接用金仓驱动连 Proxy大概率会握手失败或者认证失败。接入场景推荐方案原因单个 Java 应用需要分片加密ShardingSphere-JDBC嵌入式无协议问题排查简单多个应用共享同一套分片规则先 JDBC 配置中心再评估 Proxy多应用一致性问题优先靠配置管理解决非 Java 应用需要分片能力Proxy 金仓 PG 兼容协议端口需要提前验证协议连通性风险较高要求全链路透明免改造不推荐 ShardingSphere建议评估数据库内置分布式方案如果你的团队确实需要 Proxy我的建议是先确认金仓 8.6 能不能开启兼容 PG 协议的对外端口然后写一个最小客户端做一次连接测试通过后再谈 Proxy 部署。6.3 主键策略别让 sequence 成为瓶颈分片场景下主键生成是经常被遗忘的一环。许多业务从 Oracle 迁到金仓时习惯用SERIAL或NEXTVAL(seq_xxx)但在 ShardingSphere 分片架构里单库 sequence 生成的全局唯一 ID 会冲突多库 sequence 又很难保证顺序一致。更合适的方式是使用 ShardingSphere 的分布式主键生成器。我的推荐是 SNOWFLAKEkeyGenerators: snowflake: type: SNOWFLAKE props: worker-id: 2配套的表结构里主键列用BIGINT和雪花算法返回的 long 完全匹配。如果你的表已经用了 UUID 字符串主键那就把策略配成keyGenerators: uuid: type: UUID并保证主键列是VARCHAR(36)。实测中SNOWFLAKE 的性能和稳定性比 UUID 好尤其在金仓这种对字符串索引不占优势的数据库上能优先用雪花就用雪花。另外要留意ShardingSphere 的 SNOWFLAKE 默认会生成带时间戳的长整型如果你下游系统对 ID 长度有严格限制比如只有 MySQL 的BIGINT无符号范围那也问题不大。但如果对接的老系统用了INT类型主键雪花 ID 会溢出这时候只能改用 UUID没有别的选择。6.4 适配完成后的快速冒烟验证清单最后分享一个我每次适配新库都会跑的冒烟链路顺手整理成清单。你在金仓 8.6 上做完基本配置后按下面的顺序跑一遍能覆盖绝大多数核心风险点通过 ShardingSphere 执行一条INSERT确认分片路由和主键生成正常。按分片键执行SELECT确认能定位到正确分片。按非分片键执行SELECT确认广播查询能合并结果。连续翻三页分页数据确认无重复无漏行。对加密字段执行一次写入和查询确认密文列、明文列读写正常。查看日志中的Actual SQL确认 schema 前缀、列名大小写都符合预期。跑完这条链路适配的核心风险基本都覆盖到了剩下就是等压测暴露性能问题。我在实际项目里的体会是金仓 8.6 的兼容性并没有很多文章写得那么可怕真正让人头疼的都是环境细节schema 没对上、驱动参数照搬、历史遗留 SQL 不兼容。只要把这些细节当成一等公民对待ShardingSphere5.4.1 接人大金仓8.6这套组合是能稳定跑下去的。
返回列表