Spring Boot 内置的缓存抽象只提供了本地实现,比如 ConcurrentMapCacheManager,应用一旦部署多个实例,缓存命中率会下降,数据也可能互相覆盖。GemFire 作为 VMware 的分布式内存数据网格,可以通过 Spring Data GemFire 接入 Spring Cache 抽象,使 @Cacheable、@CachePut、@CacheEvict 直接管理远端 Region。本文以一个客户查询服务为例,说明从依赖到配置再到缓存读写的完整整合方式。

一、准备 GemFire 环境与项目依赖
GemFire 的最小部署通常包含 Locator 和 Server 两种角色。Locator 负责服务发现、负载均衡和成员管理,Server 承载真实的数据 Region。开发环境可以使用 gfsh 命令行快速拉起一个单节点集群,命令如下。
start locator --name=locator1 --port=10334 start server --name=server1 --locators=localhost[10334] create region --name=Customers --type=PARTITION_REDUNDANT --redundant-copies=1
在 Spring Boot 项目中接入 GemFire,最简单的方式是引入 spring-geode-starter。它会把 Spring Data GemFire 以及客户端缓存自动配置一并带入,减少手工装配 ClientCache 的成本。Maven 依赖如下。
<dependency>
<groupId>org.springframework.geode</groupId>
<artifactId>spring-geode-starter</artifactId>
</dependency>
版本方面,如果项目已经通过 Spring Boot BOM 统一管理依赖,可以不再单独指定版本号。但要注意不同版本的 Spring Data GemFire 对 GemFire 服务端版本有对应要求,升级前应先核对兼容矩阵,避免客户端连接不上旧版 Locator。若项目同时引入了 Redis 或 Caffeine 缓存组件,还需要排除它们自动配置的 CacheManager,避免运行时出现多个缓存管理器冲突。
二、配置 ClientCache 与 Region
主类上通过 @ClientCacheApplication 注解连接 Locator,这是 Spring Data GemFire 提供的客户端缓存声明式配置。它会在应用启动时创建一个 ClientCache 实例,并自动连接指定的 Locator 地址。与手工使用 ClientCacheFactory 相比,注解方式更适合 Spring Boot 的自动装配风格。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions;
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
import org.springframework.data.gemfire.config.annotation.Locator;
@SpringBootApplication
@ClientCacheApplication(name = "GemFireClientCache", locators = @Locator(host = "localhost", port = 10334))
@EnableEntityDefinedRegions(basePackageClasses = Customer.class)
@EnableCachingDefinedRegions
public class GemFireCacheApplication {
public static void main(String[] args) {
SpringApplication.run(GemFireCacheApplication.class, args);
}
}
上面的配置里,@EnableEntityDefinedRegions 会扫描标记了 @Region 的实体类并自动创建对应 Region,@EnableCachingDefinedRegions 则根据 Spring Cache 注解中的 value 创建缓存 Region。Customer 实体可以这样定义。
import org.springframework.data.annotation.Id;
import org.springframework.data.gemfire.mapping.annotation.Region;
@Region("Customers")
public class Customer {
@Id
private String id;
private String name;
public Customer() {
}
public Customer(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
如果 Region 已经在服务端通过 gfsh 手动创建,则要保证 Region 名称与 @Cacheable(value = "Customers") 中的 value 完全一致。对于自动创建 Region 的方式,应用启动时客户端会向 Locator 发送 Region 创建请求,因此运行前需要确保 Locator 和 Server 已经可用。
三、使用 Spring Cache 注解完成读写
接入 GemFire 之后,Service 层仍然使用标准 Spring Cache 注解,不需要直接操作 GemFire 的 API。以下示例展示了查询方法如何通过 @Cacheable 先读缓存,未命中时再查询数据库或模拟慢查询。
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
@Cacheable(value = "Customers", key = "#id", unless = "#result == null")
public Customer getCustomer(String id) {
simulateSlowService(id);
Customer customer = new Customer();
customer.setId(id);
customer.setName("name-" + id);
return customer;
}
private void simulateSlowService(String id) {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
这里的 key 表达式 #id 表示使用方法参数作为 Region 的键,unless 条件可以阻止 null 结果写入缓存,减少缓存穿透风险。首次调用 getCustomer 会执行方法体并把返回结果写入 GemFire 的 Customers Region,后续相同 id 的请求会直接命中远端缓存,不再进入方法体。
如果数据发生更新,需要使用 @CacheEvict 清除旧缓存,或者使用 @CachePut 不跳过方法执行直接更新缓存。下面给出 @CacheEvict 的简单示例。
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
@Cacheable(value = "Customers", key = "#id")
public Customer getCustomer(String id) {
return queryFromDatabase(id);
}
@CacheEvict(value = "Customers", key = "#customer.id")
public Customer updateCustomer(Customer customer) {
return saveToDatabase(customer);
}
private Customer queryFromDatabase(String id) {
Customer customer = new Customer();
customer.setId(id);
customer.setName("name-" + id);
return customer;
}
private Customer saveToDatabase(Customer customer) {
return customer;
}
}
使用 @CachePut 时要注意,方法始终会执行,并且返回值会写入缓存。如果更新方法本身需要返回最新数据,可以用它替代 @Cacheable 和 @CacheEvict 的组合。不过对于删除操作,仍然只能使用 @CacheEvict,因为它没有返回数据可以写入缓存。
四、序列化、分区与常见问题
GemFire 推荐使用 PDX 序列化而不是原生 Java 序列化。启用 @EnablePdx 后,对象会以字段结构存储,查询时无需反序列化整个对象,也具备更好的版本兼容性。配置方式很简单,只需新建一个配置类。
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.config.annotation.EnablePdx;
@Configuration
@EnablePdx
public class GemFirePdxConfig {
}
默认情况下,Region 使用 PARTITION 类型在集群中进行分片,数据分布均匀但单份存储。如果对可用性要求较高,可以参考前文 gfsh 示例创建 PARTITION_REDUNDANT 类型并设置冗余副本数。冗余副本会增加内存占用和网络同步开销,因此需要结合实际数据规模评估,通常设置一份冗余即可满足多数场景。
实际项目中还有一个常见问题是键的序列化。使用 Spring Cache 注解时,方法参数会作为 GemFire Region 的键,键类型应尽量保持一致。比如不要在一次调用中传入 String,另一次传入 Long,否则可能因为序列化类型不同而导致缓存查找失败。同时要避免将大对象直接作为键,建议使用业务主键或可读性高的组合键。
从长期维护角度看,客户端连接池、Socket 缓冲区大小和 Region 过期策略都需要关注。GemFire 支持在 Region 上配置 TTL 和 TTI,可以在创建 Region 时通过 @EnableExpiration 或自定义 RegionConfigurer 完成。最后,监控缓存命中率、服务端 Region 大小以及客户端连接数,能够帮助你及时发现缓存穿透、热点数据和客户端资源泄漏等问题。
Spring BootSpring Data GemFire分布式缓存修改时间:2026-09-22 03:02:32