CacheSQL(四):CacheSQLClient——用一张路由表实现水平扩展
CacheSQL四CacheSQLClient——用一张路由表实现水平扩展前三篇讲了 CacheSQL 内部的架构工程化升级、主从复制、双引擎与 SQL 查询。但还有一个问题没解决——再多节点对调用方来说还是一条 HTTP URL 吗不是。调用方需要知道哪个表在哪个节点上、Master 是谁、Slave 有谁、读操作打哪个、写操作打哪个。这些不应该让调用方自己管理——应该封装在一个 Client 里。这就是 CacheSQLClient。一、一张路由表隐掉所有拓扑配置# 组 insurance负责 KCA2、KCA3 两张表 cachesql.group.insurance.masterhttp://192.168.1.10:8080 cachesql.group.insurance.slaveshttp://192.168.1.11:8080,http://192.168.1.12:8080 cachesql.group.insurance.tablesKCA2,KCA3 # 组 finance负责 KCA4 一张表 cachesql.group.finance.masterhttp://192.168.2.10:8080 cachesql.group.finance.slaveshttp://192.168.2.11:8080 cachesql.group.finance.tablesKCA4调用方只需要知道三件事CacheSQLClientclientnewCacheSQLClient(cachesql.properties);// 查——自动打到 Master 或任意 SlaveListMapString,Objectrowsclient.get(KCA2,AAC001,12345);// 写——自动打到 Masterbooleanokclient.insert(KCA2,AAC001,99999,data);不需要知道 KCA2 在哪台机器上不需要区分读操作打谁、写操作打谁。Client 内部按 Table → Group → URL 的路由链自动完成寻址。二、路由逻辑读分流写集中privatestaticclassTableGroup{finalStringmaster;finalString[]slaves;// 读Master 或任意 Slave各 50% 概率StringpickReadUrl(){if(slaves.length0)returnmaster;returnrandom()?master:slaves[(int)(Math.random()*slaves.length)];}// 写始终 MasterStringgetWriteUrl(){returnmaster;}}简单但有效。读操作随机分摊到组内所有节点Master SlavesMaster 也不闲置。写操作始终打到 Master——因为只有 Master 有写入权限Slave 的写操作最终也是转发给 Master。这个设计借鉴了 MySQL 读写分离的经典模式写走 Master读分摊。但因为 CacheSQL 是内存库没有主从延时的问题——一切都是同步写入后广播读任意节点都是一致的数据。三、按表分组不同表走不同节点不是所有表都在一组机器上。card_info 表数据量大、QPS 高可能需要独立一组节点。dict 表数据量小、低频访问可以和其他表共用一组。TableGroup 的设计支持了这种灵活性privatefinalMapString,TableGrouptableToGroupnewLinkedHashMap();privateTableGroupfindGroup(StringtableName){TableGroupgrouptableToGroup.get(tableName);if(groupnull)thrownewIllegalArgumentException(No group configured for table: tableName);returngroup;}每个表通过配置文件绑定到一个组。调用方传表名Client 自动路由到对应的组。这是传统分库分表中间件的简化版——不做数据拆分只做路由隔离。四、自带的 JSON 解析器零外部依赖CacheSQL 的服务器返回的是 JSON。正常做法是用 Jackson 或 Gson 反序列化。但 CacheSQLClient 只依赖 JDK 标准库——自己实现了一个 JSON 解析器。这不是为了炫技。是为了零依赖——调用方不需要为了用 CacheSQL 引入任何额外的 JAR 包。一个CacheSQLClient.java扔进项目就能用。实现方式逐字符扫描。区分字符串内/外、跟踪花括号深度、按逗号切字段。对象嵌套用matchBrace递归匹配括号对确定边界——不是完整的 JSON 解析器但覆盖了服务器返回的扁平对象数组格式。privateintmatchBrace(Strings,intstart){intdepth0;booleaninStrfalse;for(intistart;is.length();i){charcs.charAt(i);if(inStr){if(c\\)i;elseif(c)inStrfalse;}else{if(c)inStrtrue;elseif(c{)depth;elseif(c}){depth--;if(depth0)returni;}}}return-1;}这是够用主义者的做法——不写完整的 JSON 规范实现只处理 CacheSQL 服务器实际返回的格式。工程上正确。五、Client 与服务器之间的分工CacheSQLClient 把三件事集中在一个类里路由发现从配置文件读取 Group 信息负载均衡读操作随机分摊到组内所有节点协议适配HTTP GET/POST JSON 解析三件事分开都能做——路由用 Nginx负载用 HAProxy协议用 Jackson。但合在一起的好处是调用方的 pom.xml 不需要加任何依赖。一行new CacheSQLClient(cachesql.properties)全部搞定。代价是什么配置方式的灵活性——分组规则必须写在 properties 文件里不能动态从注册中心发现。但在政务系统的部署环境下节点拓扑是静态的——配置文件的方式反而更可控。六、水平扩展的完整方案回顾 CacheSQL 的水平扩展全貌CacheSQLClient (路由表 读负载均衡) │ ┌────────┼────────┐ ▼ ▼ ▼ ┌───────┐┌───────┐┌───────┐ │Master ││Slave 1││Slave 2│ ← Group: insurance │.10 ││.11 ││.12 │ └───┬───┘└───────┘└───────┘ │ 广播 ┌───┴───┐┌───────┐┌───────┐ │Master ││Slave 1││Slave 2│ ← Group: finance │.20 ││.21 ││.22 │ └───────┘└───────┘└───────┘Client 层表名 → 组名 → URL调用方零感知Group 层一主多从总共可以定义任意多个组Server 层Master 写入 广播Slave 接收回放水平扩展不是给你无限 QPS 的——它是让你在想加节点的时候不用改代码。下一篇[桥接篇从练手到交付——四篇博客之后我回头做了 CacheSQL]系列CacheSQL 工程化交付实录共 5 篇含桥接篇