`
dannyhz
  • 浏览: 376782 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

spring ehcache redis 两级缓存的方案

 
阅读更多
http://blog.csdn.net/liaoyulin0609/article/details/51787020


引用


在上篇《性能优化-缓存篇》中已经从理论上介绍了缓存,其实,缓存简单易用,更多精力关注的是根据实际业务的选择缓存策略。

本文主要介绍为什么要构建ehcache+redis两级缓存?以及在实战中如何实现?思考如何配置缓存策略更合适?这样的方案可能遗留什么问题?JUST DO IT! GO!

问题描述

场景:我们的应用系统是分布式集群的,可横向扩展的。应用中某个接口操作满足以下一个或多个条件:
1. 接口运行复杂代价大,
2. 接口返回数据量大,
3. 接口的数据基本不会更改,
4. 接口数据一致性要求不高(只需满足最终一致)。
此时,我们会考虑将这个接口的返回值做缓存。考虑到上述条件,我们需要一套高可用分布式的缓存集群,并具备持久化功能,备选的有ehcache集群或redis主备(sentinel)。

ehcache集群因为节点之间数据同步通过组播的方式,可能带来的问题:节点间大量的数据复制带来额外的开销,在节点多的情况下此问题越发严重,N个节点会出现N-1次网络传输数据进行同步。(见下图)

redis主备由于作为中心节点提供缓存,其他节点都向redis中心节点取数据,所以,一次网络传输即可。(当然此处的一次网络代价跟组播的代价是不一样的)但是,随着访问量增大,大量的缓存数据访问使得应用服务器和缓存服务器之间的网络I/O消耗越大。(见下图)

提出方案

那么要怎么处理呢?所以两级缓存的思想诞生了,在redis的方案上做一步优化,在缓存到远程redis的同时,缓存一份到本地进程ehcache(此处的ehcache不用做集群,避免组播带来的开销),取缓存的时候会先取本地,没有会向redis请求,这样会减少应用服务器<–>缓存服务器redis之间的网络开销。(见下图)

如果用过j2cache的都应该知道,oschina用j2cache这种两级缓存,实践证明了该方案是可行的。该篇使用spring+ehcache+redis实现更加简洁。

方案实施

1、 spring和ehcache集成

主要获取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
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2、 spring和redis集成

主要获取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
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
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
3、 spring集成ehcache和redis

通过上面两步注入的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
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
4、 模拟问题中提到的接口

此处假设该接口满足上述条件。

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
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
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
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
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
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
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)》


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics