您的位置:首页 > 数据库 > Redis

SpringBoot整合ehcache和redis实现二级缓存

2018-12-13 10:23 2021 查看

简要说明:ehcache是内存缓存,在本地jvm内存中,十分高效,但是如果缓存数据都存在jvm中,内存是不够用的,于是使用到了redis数据库缓存,redis是键值对数据库,也比较高效,如果仅用redis做缓存,则存在频繁的网络IO读写,因为一般的会将redis部署在一个单独的服务器上,或者是集群部署。所以我们结合两者的特性,优先使用ehcache缓存,当ehcache中没有数据时,再向redis中取,redis中取到数据后,并把数据再次存到ehcache缓存中。总体的设计就是讲ehcache的失效时间设置比较短,将redis缓存失效时间设置的比较长,这样就可以充分发挥两者的特性了。

废话不说上代码:

1.redis 集群配置

[code]@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<RedisNode> redislist = new ArrayList<RedisNode>();
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<String, String> 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如何集群部署,请参考连接

[code]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,下文中会使用到

[code]<ehcache updateCheck="false">

<!-- Sets the path to the directory where cache .data files are created.
If the path is a Java System Property it is replaced by its value in the
running VM. The following properties are translated: user.home - User's home
directory user.dir - User's current working directory java.io.tmpdir - Default
temp file path -->
<diskStore path="java.io.tmpdir"/>

<!--Default Cache configuration. These will applied to caches programmatically
created through the CacheManager. The following attributes are required:
maxElementsInMemory - Sets the maximum number of objects that will be created
in memory eternal - Sets whether elements are eternal. If eternal, timeouts
are ignored and the element is never expired. overflowToDisk - Sets whether
elements can overflow to disk when the in-memory cache has reached the maxInMemory
limit. The following attributes are optional: timeToIdleSeconds - Sets the
time to idle for an element before it expires. i.e. The maximum amount of
time between accesses before an element expires Is only used if the element
is not eternal. Optional attribute. A value of 0 means that an Element can
idle for infinity. The default value is 0. timeToLiveSeconds - Sets the time
to live for an element before it expires. i.e. The maximum time between creation
time and when an element expires. Is only used if the element is not eternal.
Optional attribute. A value of 0 means that and Element can live for infinity.
The default value is 0. diskPersistent - Whether the disk store persists
between restarts of the Virtual Machine. The default value is false. diskExpiryThreadIntervalSeconds-
The number of seconds between runs of the disk expiry thread. The default
value is 120 seconds. -->

<defaultCache maxElementsInMemory="100000" eternal="false"
overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120"
diskPersistent="false" diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LFU"/>
<cache name="L3_EHCAHCE" maxElementsInMemory="100000" eternal="false"
overflowToDisk="false" timeToIdleSeconds="0" timeToLiveSeconds="259200">
</cache>
<cache name="HealthRedis" maxElementsInMemory="20" eternal="false"
overflowToDisk="false" timeToIdleSeconds="0" timeToLiveSeconds="1200" />

<cache name="cacheTest" maxElementsInMemory="200" eternal="false"
overflowToDisk="false" timeToIdleSeconds="10" timeToLiveSeconds="20"
memoryStoreEvictionPolicy="LFU" />
<cache name="cacheTest2" maxElementsInMemory="200" eternal="false"
overflowToDisk="false" timeToIdleSeconds="10" timeToLiveSeconds="20"
memoryStoreEvictionPolicy="LFU" />
</ehcache>

3,整合redis和ehcache

spring 中关于cache的注解主要为@CacheAble,@CachePut,@CacheEvit这三个注解,依次为放入,刷新,失效缓存,而核心的两个接口是CacheManager与Cache,这里实现cache接口,也是整合的核心实现类

[code]
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

[code]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<EhredisCache> caches = new ArrayList<EhredisCache>();

List<ACacheCore> aCaches = new ArrayList<ACacheCore>();
ACacheCore aCacheCore = new ACacheCore();
//EhredisCache ehredisCache = new EhredisCache();
RedisTemplate<String, String> 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;
}

}
[code]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 

[code]test.ehcache.name=cacheTest
test.redis.expiration=60

这里的60指的是redis中TTL为60秒,关于ehcache的失效时间在ehcache2.xml中有配置

还贴一下获取redisTemplate的类吧

[code]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<String, String> redisTemplate;

public void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}

public RedisTemplate<String, String> getRedisTemplate()
{
return this.redisTemplate;
}

}

至此,关于配置我们全部完成了。

应用和测试

写一个controller

[code]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

[code]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方法

[code]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也贴出来吧,

[code]<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>songhq.com.cache</groupId>
<artifactId>cache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cache</name>
<description>常见的缓存的demo</description>
<parent>
<!--SpringBoot相关的组件版本都受控制 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!-- springCloud对应的版本 -->
<spring-cloud.version>Edgware.SR1</spring-cloud.version>
</properties>

<dependencies>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.47</version>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>

<!--引入commons-httpclient  -->
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>

<!-- 引入common-lang包 -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!--引入redis依赖  -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<!-- <version>2.7.3</version> --><!--spring boot里面已经对他进行了版本控制  -->
</dependency>
<!-- ehcache spring 自带的EhcacheManager是在spring-context 和spring-context-support模块中 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<!-- <version>2.10.3</version> -->
</dependency>
<!-- 引入mongo相关依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--读取配置文件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- spring Cloud相关的组件版本控制 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<!--maven插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugin</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

如要完整代码请参见本人github地址,里面还有其他关于Spring Cloud的学习项目,不要忘记点赞

 https://github.com/songhq211949/springCloud.git  里面的cache项目,如有问题请多多指教

 

 

 

 

 

 

 

 

 

 

 

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: