Ehcache是一款优秀的缓存中间件,具体介绍可查阅官网http://www.ehcache.org/。目前已经出到3.X版本,但主流的基于Ehcache的分布式缓存方案还是以2.X为主。
Ehcache作为分布式缓存经过测试存在以下两个问题:
1.作为Springboot缓存方案,即通过@Cacheable实现的缓存,只能缓存本地,无法实现分布式。
2.手动可以实现分布式缓存,但没有分布式锁功能,无法实现数据的强一致性。
后来发现存在一种更好的分布式缓存方案——Hazelcast,具体介绍可见官网https://hazelcast.com/。Hazelcast完美地解决了上述问题,因此本文只是对Springboot集成Ehcache做个归档,日后如果需要使用嵌入式的分布式缓存,会使用Hazelcast。
Springboot集成Ehcache步骤如下:
1.添加依赖,包括jgroup分布式缓存依赖和hibernate二级缓存依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.4.0.Final</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-jgroupsreplication</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.jgroups</groupId>
<artifactId>jgroups</artifactId>
<version>3.6.3.Final</version>
</dependency>
2.配置文件ehcache.xml,置于resources目录下
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<diskStore path="java.io.tmpdir/one"/>
<cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory" properties="connect=UDP(mcast_addr=231.12.21.132;mcast_port=45566):PING:
MERGE2:FD_SOCK:VERIFY_SUSPECT:pbcast.NAKACK:UNICAST:pbcast.STABLE:FRAG:pbcast.GMS" propertySeparator="::"/>
<cache name="userCache" maxEntriesLocalHeap="1000" eternal="false" timeToIdleSeconds="100" timeToLiveSeconds="100" overflowToDisk="false">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory" properties="replicateAsynchronously=false,replicatePuts=true,replicateUpdates=true,replicateUpdatesViaCopy=true,replicateRemovals=true"/>
</cache>
</ehcache>
3.配置application.properties
#缓存配置
spring.cache.type=ehcache
spring.cache.ehcache.config=classpath:ehcache.xml
#二级缓存
spring.jpa.properties.hibernate.generate_statistics=true
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.use_query_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
spring.jpa.properties.javax.persistence.sharedCache.mode=ENABLE_SELECTIVE
4.测试方法缓存,启动类上添加@EnableCaching
/**
* 只具备本地缓存功能,适用场景:不涉及方法外变量和数据库的操作
*/
@ApiOperation("测试@Cacheable")
@Cacheable(cacheNames = "userCache", sync = true)
@GetMapping("/user")
public User cacheable(@RequestParam("id") long id, @RequestParam("username") String username) throws BizException {
log.info("没走缓存");
User user = userRepository.findById(id).orElse(null);
if (user == null) {
throw new BizException("id为"+id+"的用户不存在");
}
user.setUsername(username);
log.info(user.toString());
userRepository.save(user);
return user;
}
相同参数请求下,第一次不会走缓存,之后都会走缓存。再起一个相同的服务组成集群,使用相同的参数进行请求,发现第一次也会走缓存,说明这种方式下不支持分布式缓存。
5.测试分布式缓存
@Autowired
private CacheManager cacheManager;
/**
* 分布式缓存需要手动设置
*/
@ApiOperation("测试分布式缓存")
@GetMapping("/ehcache")
public User ehcache(@RequestParam long id) {
Cache cache = cacheManager.getCache("userCache");
// 锁只对本地线程有效,分布式下不起作用
cache.acquireWriteLockOnKey("user");
Element e = cache.get("user");
User user;
if (e == null) {
log.info("没有使用缓存");
user = userRepository.findById(id).get();
} else {
log.info("使用了缓存");
user = (User) e.getObjectValue();
}
cache.put(new Element("user", user));
cache.releaseWriteLockOnKey("user");
return user;
}
需要注意,此处注入的CacheManager是Ehcache的CacheManager,而不是Springboot的CacheManager。
6.测试Ehcache作为二级缓存
在实体类上添加如下注解:
@Cacheable //实体类二级缓存
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) //默认可不配
请求时可以在控制台看到查询日志,显示命中率等信息。