Apache Geode 常被用来做分布式内存数据网格,而 SQLite 是本地嵌入式数据库,两者看起来不搭。但在实际项目中,把两者组合能解决一个典型问题:边缘节点或应用实例既需要 Geode 的高吞吐数据共享,又希望在断网或进程重启时保留一份本地可查的数据副本。

一、先厘清两者定位和组合价值
Apache Geode 的核心能力是把数据分散到多个节点的内存中,并通过分区和冗余机制提供低延迟访问。它适合保存频繁读写的热数据,比如用户会话、实时价格、库存快照。问题在于 Geode 的数据虽然可以持久化到磁盘,但其主要设计目标不是单机嵌入式存储,客户端如果和集群断开会失去数据访问能力。
SQLite 的强项正好补上这个缺口。它不需要独立数据库进程,一个文件就是完整数据库,支持标准SQL和事务。把 SQLite 放到应用进程内部,可以让应用在无法连接 Geode 集群时继续读写本地数据,等网络恢复后再同步。这个思路在边缘计算、桌面应用、离线POS系统里非常实用。
组合方案不是要用 SQLite 替代 Geode 的分布式能力,而是让它承担本地缓存、离线暂存、启动预热和审计日志这些职责。只要把同步边界设计清楚,就能获得两种技术的好处,同时避免在应用层维护复杂的文件协议。
二、实战项目中的依赖、表结构和同步策略
项目使用 Java 11 作为运行时,通过 SQLite JDBC 驱动访问本地库,并引入 Apache Geode 的客户端依赖。构建文件中的关键依赖如下。
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.45.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.geode</groupId>
<artifactId>geode-core</artifactId>
<version>1.15.1</version>
</dependency>
以一个简化的订单状态同步项目为例。我们假设 Geode 中有一个 Region 名为 orders,键是订单号,值是订单状态对象。应用本地用 SQLite 建一张 order_sync_log 表,记录每次状态变更。建表语句如下。
CREATE TABLE IF NOT EXISTS order_sync_log (
order_id TEXT PRIMARY KEY,
status TEXT NOT NULL,
update_time INTEGER NOT NULL,
synced INTEGER DEFAULT 0
);
这张表有两个作用:当 Geode 集群不可用时,应用可以把状态变更先写入 SQLite,并把 synced 置为0;当连接恢复后,后台线程扫描未同步记录,再写入 Geode。反过来,当 Geode 中发生变更时,通过监听器把记录更新到 SQLite,保证本地缓存与远端一致。
同步策略推荐采用 write-behind 而不是 write-through。write-through 会在每次写 Geode 时同步写 SQLite,虽然一致性强,但会拖慢主路径。write-behind 让 Geode 写入成功后异步更新本地库,偶尔宕机可能丢失最后一条日志,但对大多数状态同步场景足够。如果要求严格,可以在业务层显式等待 SQLite 提交。
下面是一个使用 Geode CacheListener 的示例。它监听 Region 的创建和更新事件,把数据写入本地 SQLite。代码中的异常被捕获后,可以记录到应用日志并触发重试。
import org.apache.geode.cache.CacheListener;
import org.apache.geode.cache.EntryEvent;
import org.apache.geode.cache.RegionEvent;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class OrderSyncListener implements CacheListener<String, String> {
private final String dbUrl;
public OrderSyncListener(String dbUrl) {
this.dbUrl = dbUrl;
}
private Connection getConnection() throws Exception {
return DriverManager.getConnection(dbUrl);
}
@Override
public void afterCreate(EntryEvent<String, String> event) {
persist(event.getKey(), event.getNewValue());
}
@Override
public void afterUpdate(EntryEvent<String, String> event) {
persist(event.getKey(), event.getNewValue());
}
private void persist(String orderId, String status) {
String sql = "INSERT OR REPLACE INTO order_sync_log(order_id, status, update_time, synced) " +
"VALUES (?, ?, ?, 1)";
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, orderId);
ps.setString(2, status);
ps.setLong(3, System.currentTimeMillis());
ps.executeUpdate();
} catch (Exception e) {
throw new RuntimeException("Failed to persist order " + orderId, e);
}
}
@Override
public void afterDestroy(EntryEvent<String, String> event) {
// 删除本地副本的逻辑可根据业务需要补充
}
@Override
public void afterInvalidate(EntryEvent<String, String> event) { }
@Override
public void afterRegionCreate(RegionEvent<String, String> event) { }
@Override
public void afterRegionDestroy(RegionEvent<String, String> event) { }
@Override
public void afterRegionClear(RegionEvent<String, String> event) { }
@Override
public void afterRegionLive(RegionEvent<String, String> event) { }
}
这段代码展示了最核心的写入链路。注意 Geode 的监听器是按 Region 注册的,只处理指定 Region 的事件。SQLite JDBC 连接串可以指向本地文件,例如 jdbc:sqlite:C:\data\cache.db。在 Windows 路径中反斜杠必须保留,例如安装目录 C:\Geode\lib 下的驱动包需要加入应用类路径。
对于反向同步,即从 SQLite 补发到 Geode,可以启动一个定时任务。该任务查询所有 synced = 0 的记录,调用 Geode 客户端的 region.put(orderId, status),成功后把 synced 更新为1。为了减少锁竞争,建议按批次读取,例如每次取100条。
三、事务边界与性能优化
把两个存储组合在一起,最需要关注的是事务边界。Geode 的 Region 操作本身支持事务,SQLite 也支持事务,但两者没有内置的分布式事务协调器。如果试图让一次业务操作同时提交 Geode 和 SQLite,必须自己实现补偿逻辑。通常做法是先写 Geode,再写 SQLite;如果 SQLite 写失败,可以记录告警并让后台重试,而不是回滚 Geode,因为回滚分布式内存数据会引入更复杂的锁和可见性问题。
SQLite 默认的日志模式是 rollback journal,写入并发较差。为了提升异步写入吞吐,建议开启 WAL 模式。连接数据库后执行:
PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA busy_timeout=5000;
WAL 模式允许读写并发,写入先追加到 WAL 文件,再由检查点合并回主库。这对于监听器频繁写入的场景很关键。需要注意 WAL 文件 cache.db-wal 和 cache.db-shm 会一直存在,不要手动删除,否则可能损坏数据库。备份时也要连同这些文件一起处理。
从压测数据看,在单机环境下,未开 WAL 时 SQLite 顺序写入大约每秒几百条,开启 WAL 并批量提交后可以提升到每秒几千条。实际项目里建议在监听器中做批量聚合,例如把100毫秒内的多个事件合并成一次事务提交。这样既能减少磁盘 I/O,也能控制 WAL 文件增长速度。
Geode 侧也要做分区规划。对于订单这类以键为主的数据,可以使用 PARTITION_REDUNDANT 类型 Region,并设置冗余副本数为1。这样当一个节点宕机时,另一个节点仍有数据,SQLite 本地库不会成为唯一数据源。如果所有 Geode 节点都不可用,本地 SQLite 才会成为临时写入点,此时需要保证磁盘空间和 WAL 检查点策略合理。
四、适用边界与替代方案
这种组合不是万能方案。如果数据量达到 TB 级且查询模式复杂,SQLite 的单文件架构会碰到瓶颈,此时应该考虑 RocksDB 或本地 PostgreSQL 嵌入模式。如果同步延迟要求毫秒级强一致,应该选用 Geode 的持久化磁盘存储或直接使用 Geode 客户端订阅,而不是异步写 SQLite。
但如果团队规模较小,应用需要快速交付,且单节点数据量在几十 GB 以内,SQLite 几乎没有运维成本。相比引入 Kafka 或 RabbitMQ 做同步管道,直接使用 Geode 事件和 SQLite 本地表可以少维护两个中间件。对于边缘设备、移动终端、单机部署的微服务,这种轻量组合的收益尤其明显。
最后要提醒的是,SQLite 数据库文件要放在可靠磁盘上,并定期备份。因为 Geode 负责分布式内存数据,运维人员有时候会忽略本地 SQLite 文件,但离线期间写入的数据就存在这里。如果磁盘故障,未同步的数据会永久丢失。建议在后台同步完成后保留原始记录一段时间,而不是立刻删除,这样即使 Geode 侧发生误操作,也能从本地日志恢复。
SQLiteApache Geode嵌入式数据库修改时间:2026-09-21 14:54:18