打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Spring+ehcache+redis两级缓存

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

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


问题描述

场景:我们的应用系统是分布式集群的,可横向扩展的。应用中某个接口操作满足以下一个或多个条件:
1. 接口运行复杂代价大,
2. 接口返回数据量大,
3. 接口的数据基本不会更改,
4. 接口数据一致性要求不高(只需满足最终一致)。

此时,我们会考虑将这个接口的返回值做缓存。考虑到上述条件,我们需要一套高可用分布式的缓存集群,并具备持久化功能,备选的有ehcache集群redis主备(sentinel)

  • ehcache集群因为节点之间数据同步通过组播的方式,可能带来的问题:节点间大量的数据复制带来额外的开销,在节点多的情况下此问题越发严重,N个节点会出现N-1次网络传输数据进行同步。(见下图,缓存集群中有三台机器,其中一台机器接收到数据,需要拷贝到其他机器,一次input后需要copy两次,两次copy是需要网络传输消耗的)
  • redis主备由于作为中心节点提供缓存,其他节点都向redis中心节点取数据,所以,一次网络传输即可。(当然此处的一次网络代价跟组播的代价是不一样的)但是,随着访问量增大,大量的缓存数据访问使得应用服务器和缓存服务器之间的网络I/O消耗越大。(见下图,同样三台应用服务器,redis sentinel作为中心节点缓存。所谓中心,即所有应用服务器以redis为缓存中心,不再像ehcache集群,缓存是分散存放在应用服务器中,需要互相同步的,任何一台应用服务器的input,都会经过一次copy网络传输到redis,由于redis是中心共享的,那么就可以不用同步的步骤,其他应用服务器需要只需去get取即可。但是,我们会发现多了N台服务器的get的网络开销。)

提出方案

那么要怎么处理呢?所以两级缓存的思想诞生了,在redis的方案上做一步优化,在缓存到远程redis的同时,缓存一份到本地进程ehcache(此处的ehcache不用做集群,避免组播带来的开销),取缓存的时候会先取本地,没有会向redis请求,这样会减少应用服务器<–>缓存服务器redis之间的网络开销。(见下图,为了减少get这几条网络传输,我们会在每个应用服务器上增加本地的ehcache缓存作为二级缓存,即第一次get到的数据存入ehcache,后面output输出即可从本地ehcache中获取,不用再访问redis了,所以就减少了以后get的网络开销。get开销只要一次,后续不需要了,除非本地缓存过期需要再get。)


如果用过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

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

2、 spring和redis集成

主要获取redisTemplate作为操作redis的对象。

redis.properties配置信息

#host 写入redis服务器地址redis.ip=127.0.0.1#Port  redis.port=6379#Passord  #redis.password=123456#连接超时30000redis.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

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

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

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

UserServiceImpl.java

@Servicepublic 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=nullCache L2 (redis) :UserServiceImpl/findById/5=nullvisit business service findById,id:5User [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=nullCache 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)》

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Spring 思维导图,让 Spring 不再难懂(cache篇)
Shiro整合springboot,freemaker,redis(含权限系统完整源码)
Redis集群服务器
SpringBoot中引入Ehcache3.x
如何设计一个本地缓存
springMVC集成缓存框架Ehcache
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服