简要说明:ehcache是内存缓存,在本地jvm内存中,十分高效,但是如果缓存数据都存在jvm中,内存是不够用的,于是使用到了redis数据库缓存,redis是键值对数据库,也比较高效,如果仅用redis做缓存,则存在频繁的网络IO读写,因为一般的会将redis部署在一个单独的服务器上,或者是集群部署。所以我们结合两者的特性,优先使用ehcache缓存,当ehcache中没有数据时,再向redis中取,redis中取到数据后,并把数据再次存到ehcache缓存中。总体的设计就是讲ehcache的失效时间设置比较短,将redis缓存失效时间设置的比较长,这样就可以充分发挥两者的特性了。
废话不说上代码:
1.redis 集群配置
@Configuration
@PropertySource(value = "classpath:/redis.properties")
@EnableCaching
@Primary
public class RedisConfig {
//正则表达式
private Pattern p = Pattern.compile("^.+[:]\\d{1,5}\\s*$");
//配置jedisPool,访问redis服务客户端用的是jedis
@Bean(name = "jedisPoolConfig")
@Primary
public JedisPoolConfig poolCofig(@Value(value = "${redis.pool.max-idle}") int maxIdle,
@Value(value = "${redis.pool.max-total}") int maxTotal,
@Value(value = "${redis.pool.max-waitMillis}") long maxWaitMillis,
@Value(value = "${redis.pool.testOnBorrow}") boolean testOnBorrow) {
JedisPoolConfig poolCofig = new JedisPoolConfig();
poolCofig.setMaxIdle(maxIdle);//最大空闲连接数
poolCofig.setMaxTotal(maxTotal);//最大连接数
poolCofig.setMaxWaitMillis(maxWaitMillis);//最大等待时间毫秒
poolCofig.setTestOnBorrow(testOnBorrow);//是否创建instance
return poolCofig;
}
//RedisClusterConfiguration 是在spring-data-redis包下
@Bean(name = "redisClusterConfiguration")
@Primary
public RedisClusterConfiguration redisClusterConfiguration(@Value(value = "${redis.addressConfig}") String addressConfig,
@Value(value = "${redis.maxRedirects}") int maxRedirects){
List redislist = new ArrayList();
String[] ipList = addressConfig.split(",");
for(String ip : ipList){
boolean isIpPort = p.matcher(ip).matches();
if (!isIpPort) {
throw new IllegalArgumentException("ip 或 port 不合法");
}
String[] ipAndPort = ip.split(":");
RedisNode redisNode = new RedisNode(ipAndPort[0],StringUtil.stringToInteger(ipAndPort[1]));
redislist.add(redisNode);
}
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
redisClusterConfiguration.setMaxRedirects(maxRedirects);
redisClusterConfiguration.setClusterNodes(redislist);
return redisClusterConfiguration;
}
//将jedisPool和redisClusterConfiguration注入到jedisConnectionFactory
@Bean(name = "jedisConnectionFactory")
@Primary
public JedisConnectionFactory jedisConnectionFactory(@Qualifier("redisClusterConfiguration") RedisClusterConfiguration clusterConfig,
@Qualifier("jedisPoolConfig") JedisPoolConfig poolConfig) {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(clusterConfig);
jedisConnectionFactory.setPoolConfig(poolConfig);
return jedisConnectionFactory;
}
@Bean(name = "redisTemplate")
@Primary
public RedisTemplate redisTemplate(
@Qualifier("jedisConnectionFactory") RedisConnectionFactory factory) {
//指定redis模板
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setKeySerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
//生成缓存的管理类
@Bean(name = "redisCacheManager")
@Primary
public CacheManager cacheManager(@Qualifier("redisTemplate") RedisTemplate redisTemplate) {
RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
// 设置缓存过期时间
//rcm.setDefaultExpiration(60);//秒
return rcm;
}
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
其中redis.properties的内容,其中的注释是对上一行的配置说明,至于redis如何集群部署,请参考连接
redis.addressConfig=127.0.01:6380,127.0.01:6381,127.0.01:6382
redis.maxRedirects=6
#redis.timeout=3000
redis.pool.max-idle=100
#最大空闲连接数
redis.pool.max-total=2000
#最大连接数
redis.pool.max-waitMillis=-1
#没有限制
redis.pool.testOnBorrow=true
#建立instance
2.ehcache配置,为ehcache2.xml,下文中会使用到
3,整合redis和ehcache
spring 中关于cache的注解主要为@CacheAble,@CachePut,@CacheEvit这三个注解,依次为放入,刷新,失效缓存,而核心的两个接口是CacheManager与Cache,这里实现cache接口,也是整合的核心实现类
package songhq.com.cache.ehcacherediscache;
import java.util.concurrent.Callable;
import net.sf.ehcache.Element;
import org.springframework.cache.Cache;
import org.springframework.cache.ehcache.EhCacheCache;
import org.springframework.data.redis.cache.RedisCache;
public class ACacheCore
implements Cache
{
public ACacheCore()
{
name = "L2";
}
public String getName()
{
return name;
}
public Object getNativeCache()
{
return null;
}
public org.springframework.cache.Cache.ValueWrapper get(Object key)
{
org.springframework.cache.Cache.ValueWrapper value = ehCacheCache.get(key);
if(value != null)
return value;
if(checkA())
{
value = redisCacheA.get(key);
if(null != value)
ehCacheCache.put(key, value.get());
}
return value;
}
public Object get(Object key, Class type)
{
Object value = null;
try
{
value = ehCacheCache.get(key, type);
}
catch(IllegalStateException e)
{
ehCacheCache.evict(key);
}
if(value != null)
return value;
if(checkA())
try
{
value = redisCacheA.get(key, type);
if(value != null)
ehCacheCache.put(key, value);
return value;
}
catch(Exception e)
{
return null;
}
else
return null;
}
public Object get(Object key, Callable valueLoader)
{
org.springframework.cache.Cache.ValueWrapper valueWrapper = ehCacheCache.get(key);
Element element = (Element)valueWrapper.get();
if(element != null)
return element.getObjectValue();
if(checkA())
{
try
{
Object value = redisCacheA.get(key, valueLoader);
if(value != null)
ehCacheCache.get(key, valueLoader);
return value;
}
catch(Exception e)
{
return null;
}
} else
{
Object value = ehCacheCache.get(key, valueLoader);
return value;
}
}
public void put(Object key, Object value)
{
ehCacheCache.put(key, value);
if(checkA())
try
{
redisCacheA.put(key, value);
}
catch(Exception exception) { }
}
public org.springframework.cache.Cache.ValueWrapper putIfAbsent(Object key, Object value)
{
org.springframework.cache.Cache.ValueWrapper valueWra = ehCacheCache.putIfAbsent(key, value);
if(checkA())
redisCacheA.putIfAbsent(key, value);
return valueWra;
}
public void evict(Object key)
{
ehCacheCache.evict(key);
if(checkA())
try
{
redisCacheA.evict(key);
}
catch(Exception exception) { }
}
public void clear()
{
ehCacheCache.clear();
if(checkA())
try
{
redisCacheA.clear();
}
catch(Exception exception) { }
}
public boolean checkA()
{
return enabledA && isAlive("HealthRedis_aliveA");
}
private boolean isAlive(String key)
{
org.springframework.cache.Cache.ValueWrapper alive = healthCache.get(key);
if(null != alive)
{
return false;
} else
{
return true;
}
}
public EhCacheCache getEhCacheCache()
{
return ehCacheCache;
}
public void setEhCacheCache(EhCacheCache ehCacheCache)
{
this.ehCacheCache = ehCacheCache;
}
public RedisCache getRedisCacheA()
{
return redisCacheA;
}
public void setRedisCacheA(RedisCache redisCacheA)
{
this.redisCacheA = redisCacheA;
}
public EhCacheCache getHealthCache()
{
return healthCache;
}
public void setHealthCache(EhCacheCache healthCache)
{
this.healthCache = healthCache;
}
public boolean isEnabledA()
{
return enabledA;
}
public void setEnabledA(boolean enabledA)
{
this.enabledA = enabledA;
}
public void setName(String name)
{
this.name = name;
}
private String name;
private EhCacheCache ehCacheCache;
private RedisCache redisCacheA;
private EhCacheCache healthCache;
private boolean enabledA;
}
接着我们使用CacheManager的一个实现类SimpleCacheManager来实现管理我们上一步整合的cache
package songhq.com.cache.ehcacherediscache;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.ehcache.EhCacheCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
@Component
public class EhcacheRedisCacheManager {
@Autowired
private RedisFactory redisFactory;
public SimpleCacheManager getSimpleCacheManager(String ehcacheName, long longValue) {
SimpleCacheManager ehRedisCacheManager = new SimpleCacheManager();
//List caches = new ArrayList();
List aCaches = new ArrayList();
ACacheCore aCacheCore = new ACacheCore();
//EhredisCache ehredisCache = new EhredisCache();
RedisTemplate redisTemplate = redisFactory.getRedisTemplate();
RedisCache redisCacheA = new RedisCache("AB@", "AB@".getBytes(), redisTemplate, longValue);
//ehredisCache.setRedisCacheA(redisCacheA);
aCacheCore.setRedisCacheA(redisCacheA);
//将Ehcache原生的cache管理者转换一下
CacheManager cacheManager = CacheManager.create(this.getClass().getClassLoader().getResource("ehcacheRedisCache/ehcache2.xml"));
Cache ehcache = cacheManager.getCache(ehcacheName);
Cache healCache = cacheManager.getCache("HealthRedis");
EhCacheCache ehCacheCache = new EhCacheCache(ehcache);
EhCacheCache ehhealthCache = new EhCacheCache(healCache);
aCacheCore.setEnabledA(true);
aCacheCore.setHealthCache(ehhealthCache);
aCacheCore.setEhCacheCache(ehCacheCache);
aCacheCore.setName(ehcacheName);
aCaches.add(aCacheCore);
//ehredisCache.setEhCacheCache(ehCacheCache);
//caches.add(ehredisCache);
//ehRedisCacheManager.setCaches(caches);
ehRedisCacheManager.setCaches(aCaches);
return ehRedisCacheManager;
}
}
package songhq.com.cache.ehcacherediscache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* 实例化出EhcacheReids的缓存Manager
* @author Administrator
*
*/
@Configuration
@PropertySource(value = { "classpath:/ehcacheRedisCache/ehredis.properties"})
@EnableCaching
public class CacheBeanManager {
@Autowired
private EhcacheRedisCacheManager ehcacheRedisCacheManager;
@Bean("testManager")
public SimpleCacheManager getSimpleCacheManager(@Value("${test.ehcache.name}")String ehcacheName, @Value("${test.redis.expiration}")Long expiration){
return ehcacheRedisCacheManager.getSimpleCacheManager(ehcacheName, expiration);
}
}
这里我们使用到了新的配置文件,ehredis.properties,也是根据这个配置文件,我们可以实例化出多个SimpleCacheManager
test.ehcache.name=cacheTest
test.redis.expiration=60
这里的60指的是redis中TTL为60秒,关于ehcache的失效时间在ehcache2.xml中有配置
还贴一下获取redisTemplate的类吧
package songhq.com.cache.ehcacherediscache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class RedisFactory {
@Autowired
private RedisTemplate redisTemplate;
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public RedisTemplate getRedisTemplate()
{
return this.redisTemplate;
}
}
至此,关于配置我们全部完成了。
应用和测试
写一个controller
package songhq.com.cache.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import songhq.com.cache.service.EhredisService;
import songhq.com.cache.vo.MiguSession;
/**
* 测试组合缓存
* @author Administrator
*
*/
@RestController
@RequestMapping("/ehredis")
public class EhredisController {
@Autowired
private EhredisService ehredisService;
@RequestMapping("/getSession")
public MiguSession getMiguSession(){
return ehredisService.getSession("user001", "token001");
}
}
相应的service
package songhq.com.cache.service;
import java.util.Date;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import songhq.com.cache.vo.MiguSession;
@Service
public class EhredisService {
@Cacheable( value="cacheTest",cacheManager="testManager", key="'ehredis_migusession_'+#userId+'_'+#userToken")
public MiguSession getSession(String userId, String userToken) {
MiguSession miguSession = new MiguSession();
miguSession.setDate(new Date());
miguSession.setSessionId(userId+userToken);
miguSession.setUserId(userId);
miguSession.setUserToken(userToken);
return miguSession;
}
}
这里我们为了检查是不是从缓存中获取的值,我们使用了setDate(new Date());如果是从缓存中获取的,则date值不变
启动项目,执行main方法
package songhq.com.cache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
使用Chrome插件Advanced Rest Client插件发送请求,结果如下
再次访问,发现date不变,即使用到了缓存
利用Redis Desktop Manager工具访问redis ,使用flushAll清楚所有的数据
再次访问http://127.0.0.1:9044/ehredis/getSession ,结果如下
由于是集群部署,在三个redis只会有一个存入数据,另一个备份,有负载均衡的的作用,至于到底会存到那个redis中,一般的是轮询的方式,本博客不做详细说明,有兴趣的可以参见。
觉得本文有帮助的请点个赞,对了把pom.xml也贴出来吧,
4.0.0
songhq.com.cache
cache
0.0.1-SNAPSHOT
cache
常见的缓存的demo
org.springframework.boot
spring-boot-starter-parent
1.5.9.RELEASE
UTF-8
UTF-8
1.8
Edgware.SR1
org.bouncycastle
bcprov-jdk15on
1.47
org.apache.httpcomponents
httpclient
4.5.2
commons-httpclient
commons-httpclient
3.1
commons-lang
commons-lang
2.6
org.springframework.data
spring-data-redis
redis.clients
jedis
net.sf.ehcache
ehcache
org.springframework.boot
spring-boot-starter-data-mongodb
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-configuration-processor
true
org.springframework
spring-context
org.springframework
spring-context-support
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
org.apache.maven.plugin
maven-compiler-plugin
2.1
true
如要完整代码请参见本人github地址,里面还有其他关于Spring Cloud的学习项目,不要忘记点赞
https://github.com/songhq211949/springCloud.git 里面的cache项目,如有问题请多多指教