在上篇《性能优化-缓存篇》中已经从理论上介绍了缓存,其实,缓存简单易用,更多精力关注的是根据实际业务的选择缓存策略。
本文主要介绍为什么要构建ehcache+redis两级缓存?以及在实战中如何实现?思考如何配置缓存策略更合适?这样的方案可能遗留什么问题?JUST DO IT! GO!
场景:我们的应用系统是分布式集群的,可横向扩展的。应用中某个接口操作满足以下一个或多个条件:
1. 接口运行复杂代价大,
2. 接口返回数据量大,
3. 接口的数据基本不会更改,
4. 接口数据一致性要求不高(只需满足最终一致)。
此时,我们会考虑将这个接口的返回值做缓存。考虑到上述条件,我们需要一套高可用分布式的缓存集群,并具备持久化功能,备选的有ehcache集群或redis主备(sentinel)。
那么要怎么处理呢?所以两级缓存的思想诞生了,在redis的方案上做一步优化,在缓存到远程redis的同时,缓存一份到本地进程ehcache(此处的ehcache不用做集群,避免组播带来的开销),取缓存的时候会先取本地,没有会向redis请求,这样会减少应用服务器<–>缓存服务器redis之间的网络开销。(见下图,为了减少get这几条网络传输,我们会在每个应用服务器上增加本地的ehcache缓存作为二级缓存,即第一次get到的数据存入ehcache,后面output输出即可从本地ehcache中获取,不用再访问redis了,所以就减少了以后get的网络开销。get开销只要一次,后续不需要了,除非本地缓存过期需要再get。)
如果用过j2cache的都应该知道,oschina用j2cache这种两级缓存,实践证明了该方案是可行的。该篇使用spring+ehcache+redis实现更加简洁。
主要获取ehcache作为操作ehcache的对象。
ehcache.xml 代码如下:
- <ehcache updateCheck="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:noNamespaceSchemaLocation="http://ehcache.sf.net/ehcache.xsd">
- <diskStore path="java.io.tmpdir/ehcache"/>
- <!-- 默认的管理策略
- maxElementsOnDisk: 在磁盘上缓存的element的最大数目,默认值为0,表示不限制。
- eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断。
- diskPersistent: 是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false。
- diskExpiryThreadIntervalSeconds:对象检测线程运行时间间隔。标识对象状态(过期/持久化)的线程多长时间运行一次。
- -->
- <defaultCache maxElementsInMemory="10000"
- eternal="false"
- timeToIdleSeconds="3600"
- timeToLiveSeconds="3600"
- overflowToDisk="true"
- diskPersistent="false"
- diskExpiryThreadIntervalSeconds="120"
- memoryStoreEvictionPolicy="LRU"/>
- <!-- 对象无过期,一个1000长度的队列,最近最少使用的对象被删除 -->
- <cache name="userCache"
- maxElementsInMemory="1000"
- eternal="true"
- overflowToDisk="false"
- timeToIdleSeconds="0"
- timeToLiveSeconds="0"
- memoryStoreEvictionPolicy="LFU">
- </cache>
- <!-- 组播方式:multicastGroupPort需要保证与其他系统不重复,进行端口注册 -->
- <!-- 若因未注册,配置了重复端口,造成权限缓存数据异常,请自行解决 -->
- <cacheManagerPeerProviderFactory
- class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
- properties="peerDiscovery=automatic,
- multicastGroupAddress=230.0.0.1,
- multicastGroupPort=4546, timeToLive=1"/>
- <!-- replicatePuts=true | false – 当一个新元素增加到缓存中的时候是否要复制到其他的peers. 默认是true。 -->
- <!-- replicateUpdates=true | false – 当一个已经在缓存中存在的元素被覆盖时是否要进行复制。默认是true。 -->
- <!-- replicateRemovals= true | false – 当元素移除的时候是否进行复制。默认是true。 -->
- <!-- replicateAsynchronously=true | false – 复制方式是异步的(指定为true时)还是同步的(指定为false时)。默认是true。 -->
- <!-- replicatePutsViaCopy=true | false – 当一个新增元素被拷贝到其他的cache中时是否进行复制指定为true时为复制,默认是true。 -->
- <!-- replicateUpdatesViaCopy=true | false – 当一个元素被拷贝到其他的cache中时是否进行复制(指定为true时为复制),默认是true。 -->
- <cache name="webCache_LT"
- maxElementsInMemory="10000"
- eternal="false"
- overflowToDisk="false"
- timeToIdleSeconds="3600"
- timeToLiveSeconds="3600"
- memoryStoreEvictionPolicy="LRU">
- <cacheEventListenerFactory
- class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
- properties="replicateRemovals=true"/>
- <bootstrapCacheLoaderFactory
- class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"/>
- </cache>
- <cache name="webCache_ST"
- maxElementsInMemory="1000"
- eternal="false"
- overflowToDisk="false"
- timeToIdleSeconds="300"
- timeToLiveSeconds="300"
- memoryStoreEvictionPolicy="LRU">
- <cacheEventListenerFactory
- class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
- properties="replicateRemovals=true"/>
- <bootstrapCacheLoaderFactory
- class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"/>
- </cache>
- </ehcache>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
spring.xml中注入ehcacheManager和ehCache对象,ehcacheManager是需要加载ehcache.xml配置信息,创建ehcache.xml中配置不同策略的cache。
- <!-- ehCache 配置管理器 -->
- <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
- <property name="configLocation" value="classpath:ehcache.xml" />
- <!--true:单例,一个cacheManager对象共享;false:多个对象独立 -->
- <property name="shared" value="true" />
- <property name="cacheManagerName" value="ehcacheManager" />
- </bean>
- <!-- ehCache 操作对象 -->
- <bean id="ehCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
- <property name="cacheName" value="ehCache"/>
- <property name="cacheManager" ref="ehcacheManager"/>
- </bean>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
主要获取redisTemplate作为操作redis的对象。
redis.properties配置信息
- #host 写入redis服务器地址
- redis.ip=127.0.0.1
- #Port
- redis.port=6379
- #Passord
- #redis.password=123456
- #连接超时30000
- redis.timeout=30
- #最大分配的对象数
- redis.pool.maxActive=100
- #最大能够保持idel状态的对象数
- redis.pool.maxIdle=30
- #当池内没有返回对象时,最大等待时间
- redis.pool.maxWait=1000
- #当调用borrow Object方法时,是否进行有效性检查
- redis.pool.testOnBorrow=true
- #当调用return Object方法时,是否进行有效性检查
- redis.pool.testOnReturn=true
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
spring注入jedisPool、redisConnFactory、redisTemplate对象
- <!-- 加载redis.propertis -->
- <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="locations" value="classpath:redis.properties"/>
- </bean>
- <!-- Redis 连接池 -->
- <bean id="jedisPool" class="redis.clients.jedis.JedisPoolConfig">
- <property name="maxTotal" value="${redis.pool.maxActive}" />
- <property name="maxIdle" value="${redis.pool.maxIdle}" />
- <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
- <property name="testOnReturn" value="${redis.pool.testOnReturn}" />
- <property name="maxWaitMillis" value="${redis.pool.maxWait}" />
- </bean>
- <!-- Redis 连接工厂 -->
- <bean id="redisConnFactory"
- class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
- <property name="hostName" value="${redis.ip}" />
- <property name="port" value="${redis.port}" />
- <!-- property name="password" value="${redis.password}" -->
- <property name="timeout" value="${redis.timeout}" />
- <property name="poolConfig" ref="jedisPool" />
- </bean>
- <!-- redis 操作对象 -->
- <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
- <property name="connectionFactory" ref="redisConnFactory" />
- </bean>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
通过上面两步注入的ehcache和redisTemplate我们就能自定义一个方法将两者整合起来。详见EhRedisCache类。
EhRedisCache.java
- /**
- * 两级缓存,一级:ehcache,二级为redisCache
- * @author yulin
- *
- */
- public class EhRedisCache implements Cache{
- private static final Logger LOG = LoggerFactory.getLogger(UserServiceImpl.class);
- private String name;
- private net.sf.ehcache.Cache ehCache;
- private RedisTemplate<String, Object> redisTemplate;
- private long liveTime = 1*60*60; //默认1h=1*60*60
- @Override
- public String getName() {
- return this.name;
- }
- @Override
- public Object getNativeCache() {
- return this;
- }
- @Override
- public ValueWrapper get(Object key) {
- Element value = ehCache.get(key);
- LOG.info("Cache L1 (ehcache) :{}={}",key,value);
- if (value!=null) {
- return (value != null ? new SimpleValueWrapper(value.getObjectValue()) : null);
- }
- //TODO 这样会不会更好?访问10次EhCache 强制访问一次redis 使得数据不失效
- final String keyStr = key.toString();
- Object objectValue = redisTemplate.execute(new RedisCallback<Object>() {
- public Object doInRedis(RedisConnection connection)
- throws DataAccessException {
- byte[] key = keyStr.getBytes();
- byte[] value = connection.get(key);
- if (value == null) {
- return null;
- }
- //每次获得,重置缓存过期时间
- if (liveTime > 0) {
- connection.expire(key, liveTime);
- }
- return toObject(value);
- }
- },true);
- ehCache.put(new Element(key, objectValue));//取出来之后缓存到本地
- LOG.info("Cache L2 (redis) :{}={}",key,objectValue);
- return (objectValue != null ? new SimpleValueWrapper(objectValue) : null);
- }
- @Override
- public void put(Object key, Object value) {
- ehCache.put(new Element(key, value));
- final String keyStr = key.toString();
- final Object valueStr = value;
- redisTemplate.execute(new RedisCallback<Long>() {
- public Long doInRedis(RedisConnection connection)
- throws DataAccessException {
- byte[] keyb = keyStr.getBytes();
- byte[] valueb = toByteArray(valueStr);
- connection.set(keyb, valueb);
- if (liveTime > 0) {
- connection.expire(keyb, liveTime);
- }
- return 1L;
- }
- },true);
- }
- @Override
- public void evict(Object key) {
- ehCache.remove(key);
- final String keyStr = key.toString();
- redisTemplate.execute(new RedisCallback<Long>() {
- public Long doInRedis(RedisConnection connection)
- throws DataAccessException {
- return connection.del(keyStr.getBytes());
- }
- },true);
- }
- @Override
- public void clear() {
- ehCache.removeAll();
- redisTemplate.execute(new RedisCallback<String>() {
- public String doInRedis(RedisConnection connection)
- throws DataAccessException {
- connection.flushDb();
- return "clear done.";
- }
- },true);
- }
- public net.sf.ehcache.Cache getEhCache() {
- return ehCache;
- }
- public void setEhCache(net.sf.ehcache.Cache ehCache) {
- this.ehCache = ehCache;
- }
- public RedisTemplate<String, Object> getRedisTemplate() {
- return redisTemplate;
- }
- public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
- this.redisTemplate = redisTemplate;
- }
- public long getLiveTime() {
- return liveTime;
- }
- public void setLiveTime(long liveTime) {
- this.liveTime = liveTime;
- }
- public void setName(String name) {
- this.name = name;
- }
- /**
- * 描述 : Object转byte[]. <br>
- * @param obj
- * @return
- */
- private byte[] toByteArray(Object obj) {
- byte[] bytes = null;
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- try {
- ObjectOutputStream oos = new ObjectOutputStream(bos);
- oos.writeObject(obj);
- oos.flush();
- bytes = bos.toByteArray();
- oos.close();
- bos.close();
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- return bytes;
- }
- /**
- * 描述 : byte[]转Object . <br>
- * @param bytes
- * @return
- */
- private Object toObject(byte[] bytes) {
- Object obj = null;
- try {
- ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
- ObjectInputStream ois = new ObjectInputStream(bis);
- obj = ois.readObject();
- ois.close();
- bis.close();
- } catch (IOException ex) {
- ex.printStackTrace();
- } catch (ClassNotFoundException ex) {
- ex.printStackTrace();
- }
- return obj;
- }
- }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
spring注入自定义缓存
- <!-- 自定义ehcache+redis-->
- <bean id="ehRedisCacheManager" class="org.springframework.cache.support.SimpleCacheManager">
- <property name="caches">
- <set>
- <bean id="ehRedisCache" class="org.musicmaster.yulin.ercache.EhRedisCache">
- <property name="redisTemplate" ref="redisTemplate" />
- <property name="ehCache" ref="ehCache"/>
- <property name="name" value="userCache"/>
- <!-- <property name="liveTime" value="3600"/> -->
- </bean>
- </set>
- </property>
- </bean>
- <!-- 注解声明 -->
- <cache:annotation-driven cache-manager="ehRedisCacheManager"
- proxy-target-class="true" />
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
此处假设该接口满足上述条件。
UserService.java
- public interface UserService {
- User findById(long id);
- List<User> findByPage(int startIndex, int limit);
- List<User> findBySex(Sex sex);
- List<User> findByAge(int lessAge);
- List<User> findByUsers(List<User> users);
- boolean update(User user);
- boolean deleteById(long id);
- }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
UserServiceImpl.java
- @Service
- public class UserServiceImpl implements UserService{
- private static final Logger LOG = LoggerFactory.getLogger(UserServiceImpl.class);
- @Cacheable("userCache")
- @Override
- public User findById(long id) {
- LOG.info("visit business service findById,id:{}",id);
- User user = new User();
- user.setId(id);
- user.setUserName("tony");
- user.setPassWord("******");
- user.setSex(Sex.M);
- user.setAge(32);
- //耗时操作
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return user;
- }
- @Override
- public List<User> findByPage(int startIndex, int limit) {
- return null;
- }
- @Cacheable("userCache")
- @Override
- public List<User> findBySex(Sex sex) {
- LOG.info("visit business service findBySex,sex:{}",sex);
- List<User> users = new ArrayList<User>();
- for (int i = 0; i < 5; i++) {
- User user = new User();
- user.setId(i);
- user.setUserName("tony"+i);
- user.setPassWord("******");
- user.setSex(sex);
- user.setAge(32+i);
- users.add(user);
- }
- return users;
- }
- @Override
- public List<User> findByAge(int lessAge) {
- // TODO Auto-generated method stub
- return null;
- }
- //FIXME 此处将list参数的地址作为key存储,是否有问题?
- @Cacheable("userCache")
- @Override
- public List<User> findByUsers(List<User> users) {
- LOG.info("visit business service findByUsers,users:{}",users);
- return users;
- }
- @CacheEvict("userCache")
- @Override
- public boolean update(User user) {
- return true;
- }
- @CacheEvict("userCache")
- @Override
- public boolean deleteById(long id) {
- return false;
- }
- }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
User.java
- public class User implements Serializable {
- private static final long serialVersionUID = 1L;
- public enum Sex{
- M,FM
- }
- private long id;
- private String userName;
- private String passWord;
- private int age;
- private Sex sex;
- public long getId() {
- return id;
- }
- public void setId(long id) {
- this.id = id;
- }
- public String getUserName() {
- return userName;
- }
- public void setUserName(String userName) {
- this.userName = userName;
- }
- public String getPassWord() {
- return passWord;
- }
- public void setPassWord(String passWord) {
- this.passWord = passWord;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public Sex getSex() {
- return sex;
- }
- public void setSex(Sex sex) {
- this.sex = sex;
- }
- @Override
- public String toString() {
- return "User [id=" + id + ", userName=" + userName + ", passWord="
- + passWord + ", age=" + age + ", sex=" + sex + "]";
- }
- }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
我们写个测试类来模拟下
TestEhRedisCache.java
- public class TestEhRedisCache{
- public static void main(String[] args) {
- ApplicationContext context = new ClassPathXmlApplicationContext("spring-ehRedisCache.xml");
- UserService userService= (UserService) context.getBean("userServiceImpl");
- System.out.println(userService.findById(5l));
- System.out.println(userService.findById(5l));
- System.out.println(userService.findById(5l));
- System.out.println(userService.findById(5l));
- System.out.println(userService.findById(5l));
- }
- }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
TEST1 输出结果:
- Cache L1 (ehcache) :UserServiceImpl/findById/5=null
- Cache L2 (redis) :UserServiceImpl/findById/5=null
- visit business service findById,id:5
- User [id=5, userName=tony, passWord=******, age=32, sex=M]
- Cache L1 (ehcache) :UserServiceImpl/findById/5=User [id=5, userName=tony, passWord=******, age=32, sex=M]
- User [id=5, userName=tony, passWord=******, age=32, sex=M]
- Cache L1 (ehcache) :UserServiceImpl/findById/5=User [id=5, userName=tony, passWord=******, age=32, sex=M]
- User [id=5, userName=tony, passWord=******, age=32, sex=M]
- Cache L1 (ehcache) :UserServiceImpl/findById/5=User [id=5, userName=tony, passWord=******, age=32, sex=M]
- User [id=5, userName=tony, passWord=******, age=32, sex=M]
- Cache L1 (ehcache) :UserServiceImpl/findById/5=User [id=5, userName=tony, passWord=******, age=32, sex=M]
- User [id=5, userName=tony, passWord=******, age=32, sex=M]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
上面第一次访问,一级缓存ehcache和二级缓存redis都没有数据,访问接口耗时操作,打印日志:
visit business service findById,id:5
第二次之后的访问,都会访问一级缓存ehcache,此时响应速度很快。
TEST2 在TEST1结束后,我们在liveTime的时间内,也就是redis缓存还未过期再次执行,会出现以下结果
- Cache L1 (ehcache) :UserServiceImpl/findById/5=null
- Cache L2 (redis) :UserServiceImpl/findById/5=User [id=5, userName=tony, passWord=******, age=32, sex=M]
- User [id=5, userName=tony, passWord=******, age=32, sex=M]
- Cache L1 (ehcache) :UserServiceImpl/findById/5=User [id=5, userName=tony, passWord=******, age=32, sex=M]
- User [id=5, userName=tony, passWord=******, age=32, sex=M]
- Cache L1 (ehcache) :UserServiceImpl/findById/5=User [id=5, userName=tony, passWord=******, age=32, sex=M]
- User [id=5, userName=tony, passWord=******, age=32, sex=M]
- Cache L1 (ehcache) :UserServiceImpl/findById/5=User [id=5, userName=tony, passWord=******, age=32, sex=M]
- User [id=5, userName=tony, passWord=******, age=32, sex=M]
- Cache L1 (ehcache) :UserServiceImpl/findById/5=User [id=5, userName=tony, passWord=******, age=32, sex=M]
- User [id=5, userName=tony, passWord=******, age=32, sex=M]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
由于TEST1执行完结束后,ehcache为进程间的缓存,自然随着运行结束而释放,所以TEST2出现:
Cache L1 (ehcache) :UserServiceImpl/findById/5=null
然而在第二次访问二级缓存redis,还未到缓存过期时间,所以在redis中找到数据(同时数据入一级缓存ehcache):
Cache L2 (redis) :UserServiceImpl/findById/5=User [id=5, userName=tony, passWord=**, age=32, sex=M]
此处不会visit….没有经过接口的耗时操作,接下来数据都可以在本地缓存ehcache中获取。
经过demo实践结果符合预期效果,还需更大规模的测试。遗留了几个问题,在代码处的TODO和FIXME中,留给大家一起来思考,一起来探讨解决。问题解决和源码下载:《spring + ehcache + redis两级缓存实战篇(2)》
百度搜索“就爱阅读”,专业资料,生活学习,尽在就爱阅读网92to.com,您的在线图书馆!
来源: http://www.92to.com/bangong/2017/11-23/31616333.html