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

简要说明: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插件发送请求,结果如下

SpringBoot整合ehcache和redis实现二级缓存_第1张图片

再次访问,发现date不变,即使用到了缓存

利用Redis Desktop Manager工具访问redis ,使用flushAll清楚所有的数据

SpringBoot整合ehcache和redis实现二级缓存_第2张图片

再次访问http://127.0.0.1:9044/ehredis/getSession ,结果如下

SpringBoot整合ehcache和redis实现二级缓存_第3张图片

由于是集群部署,在三个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项目,如有问题请多多指教

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(缓存)