`
dannyhz
  • 浏览: 394818 次
  • 性别: 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)》


分享到:
评论

相关推荐

    spring + ehcache + redis两级缓存

    当我们谈论“Spring + Ehcache + Redis”两级缓存时,我们实际上是在讨论如何在Java环境中利用Spring框架来集成Ehcache作为本地缓存,并利用Redis作为分布式二级缓存,构建一个高效且可扩展的缓存解决方案。...

    springMybatis+redis三级缓存框架

    "springMybatis+redis三级缓存框架"是一个高效且灵活的解决方案,它将MyBatis的二级缓存与Redis相结合,形成一个三级缓存体系,以优化数据读取速度并减轻数据库压力。 首先,MyBatis作为一款轻量级的持久层框架,其...

    spring-ehcache-redis两级缓存

    两级缓存在redis的方案上做一步优化,在缓存到远程redis的同时,缓存一份到本地进程ehcache(此处的ehcache不用做集群,避免组播带来的开销),取缓存的时候会先取本地,没有会向redis请求,这样会减少应用服务器&lt;–...

    spring boot+spring cache实现两级缓存(redis+caffeine)

    "Spring Boot+Spring Cache实现两级缓存(Redis+Caffeine)" 知识点一:缓存与两级缓存 缓存是将数据从读取较慢的介质上读取出来放到读取较快的介质上,如磁盘--&gt;内存。平时我们会将数据存储到磁盘上,如:数据库。...

    spring+redis作为缓存,带springTest配置

    Spring的缓存抽象层允许开发者选择不同的缓存提供商,如Redis、EhCache等,无需深入学习每个缓存系统的细节。 Redis则是一个高性能的键值存储系统,常用于数据库、缓存和消息代理。它的特点是速度快、数据结构丰富...

    springboot+jpa(hibernate配置redis为二级缓存) springboot2.1.4

    在本文中,我们将深入探讨如何在Spring Boot 2.1.4.RELEASE项目中结合JPA(Java Persistence API)和Hibernate实现Redis作为二级缓存。首先,我们需要理解这些技术的基本概念。 Spring Boot 是一个用于简化Spring...

    SpringCache与redis集成,优雅的缓存解决方案.docx

    SpringCache与Redis集成,优雅的缓存解决方案 SpringCache是一种基于Java的缓存解决方案,它可以与Redis集成,提供了一种优雅的缓存解决方案。在本文中,我们将对SpringCache与Redis集成的优雅缓存解决方案进行详细...

    Spring Boot整合Redis做集中式缓存

    在一些要求高一致性(任何数据变化都能及时的被查询到)的系统和应用中,就不能再使用EhCache来解决了,这个时候使用集中式缓存是个不错的选择,因此本文将介绍如何在Spring Boot的缓存支持中使用Redis进行数据缓存...

    springboot 整合ehcache+redis 通过配置文件切换缓存类型

    1、springboot 整合ehcache+redis 通过配置文件application.yml切换缓存类型 2、ehcache 、redis 通过缓存管理器管理 3、可分别设置缓存的过期时间 ehcache :添加依赖 pom.xml 2、添加配置文件ehcache.xml 3、添加...

    浅谈SpringCache与redis集成实现缓存解决方案

    "浅谈SpringCache与redis集成实现缓存解决方案" SpringCache是Spring Framework中的一种缓存机制,可以与Redis集成实现缓存解决方案。Redis是一种key-value型数据库,具有高性能、低延迟的特点,广泛应用于缓存...

    Spring Redis缓存实例

    Spring集成Redis缓存能有效提升应用性能,减少数据库压力。通过合理配置和使用,可以在保证数据一致性的前提下,为用户提供快速响应。理解并掌握Spring Cache和Redis的集成,是优化Java Web应用的关键技能。

    分布式多级缓存实践

    本文将详细探讨基于Redis和Ehcache的两级缓存实现方案,以及如何与Spring服务相结合,提高系统性能。 首先,我们来看Ehcache。Ehcache是一款广泛使用的Java缓存库,它支持本地内存缓存,具有快速响应和较低延迟的...

    springboot整合Ehcache组件,轻量级缓存管理

    Ehcache缓存简介 1、基础简介 EhCache是一个纯Java的进程内缓存框架,具有快速、上手简单等特点,是Hibernate中默认的缓存提供方。 2、Hibernate缓存 ...4、对比Redis缓存 Ehcache:直接在Jvm虚拟机中缓存

    springboot1.x基于spring注解实现J2Cache两级缓存集成

    在本文中,我们将深入探讨如何在Spring Boot 1.x版本中使用Spring注解来实现J2Cache的两级缓存机制,其中包括一级缓存Ehcache和二级缓存Redis。通过这种方式,我们可以显著提高应用程序的性能,减少对数据库的依赖,...

    SpringAOP结合ehCache实现简单缓存实例

    在IT行业中,Spring AOP(面向切面编程)和EhCache是两个非常重要的概念,它们在提升应用程序性能和管理缓存方面发挥着关键作用。本文将深入探讨如何结合Spring AOP与EhCache实现一个简单的缓存实例,以便优化Java...

    memcached完全剖析ehcache memcached redis 缓存技术总结

    标题中的“memcached完全剖析ehcache memcached redis 缓存技术总结”涵盖了三种常见的缓存技术:Memcached、Ehcache和Redis。这三种技术在IT行业中被广泛应用于提高应用程序性能,通过存储和检索频繁访问的数据来...

    J2Cache - 基于内存和 Redis 的两级 Java 缓存框架

    J2Cache 是一个为 Java 应用程序设计的两级缓存框架,旨在提供高效、可靠的缓存解决方案。它结合了本地内存缓存(如 Ehcache 或 Caffeine)与分布式缓存系统(如 Redis),在确保数据高速访问的同时,减轻了完全依赖...

    Ehcache+xmemcached+redis 整合spring注解demo

    1. **Ehcache**: Ehcache是Java中广泛使用的内存缓存库,它提供了一个快速且轻量级的解决方案来存储和检索数据。Ehcache支持本地内存缓存、磁盘持久化以及分布式缓存模式。在Spring框架中,可以通过注解配置将...

    spring简单的缓存

    Spring框架提供了强大的缓存抽象,支持多种缓存机制,如 EhCache、Redis、Hazelcast 和 Infinispan 等。在Spring中实现缓存,通常涉及以下几个步骤: 1. **启用缓存**: 在Spring配置文件中,我们需要启用缓存管理...

    spring整合EhCache 的简单例子

    Spring 整合 EhCache 是一个常见的缓存管理方案,它允许我们在Spring应用中高效地缓存数据,提高系统性能。EhCache 是一个开源、基于内存的Java缓存库,适用于快速、轻量级的数据存储。现在我们来详细探讨如何在...

Global site tag (gtag.js) - Google Analytics