小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Spring+ehcache+redis兩級緩存

 WindySky 2017-11-23

在上篇《性能優(yōu)化-緩存篇》中已經(jīng)從理論上介紹了緩存,其實,緩存簡單易用,更多精力關(guān)注的是根據(jù)實際業(yè)務(wù)的選擇緩存策略。

本文主要介紹為什么要構(gòu)建ehcache+redis兩級緩存?以及在實戰(zhàn)中如何實現(xiàn)?思考如何配置緩存策略更合適?這樣的方案可能遺留什么問題?JUST DO IT! GO!


問題描述

場景:我們的應(yīng)用系統(tǒng)是分布式集群的,可橫向擴(kuò)展的。應(yīng)用中某個接口操作滿足以下一個或多個條件:
1. 接口運行復(fù)雜代價大,
2. 接口返回數(shù)據(jù)量大,
3. 接口的數(shù)據(jù)基本不會更改,
4. 接口數(shù)據(jù)一致性要求不高(只需滿足最終一致)。

此時,我們會考慮將這個接口的返回值做緩存。考慮到上述條件,我們需要一套高可用分布式的緩存集群,并具備持久化功能,備選的有ehcache集群redis主備(sentinel)。

  • ehcache集群因為節(jié)點之間數(shù)據(jù)同步通過組播的方式,可能帶來的問題:節(jié)點間大量的數(shù)據(jù)復(fù)制帶來額外的開銷,在節(jié)點多的情況下此問題越發(fā)嚴(yán)重,N個節(jié)點會出現(xiàn)N-1次網(wǎng)絡(luò)傳輸數(shù)據(jù)進(jìn)行同步。(見下圖,緩存集群中有三臺機器,其中一臺機器接收到數(shù)據(jù),需要拷貝到其他機器,一次input后需要copy兩次,兩次copy是需要網(wǎng)絡(luò)傳輸消耗的)
    這里寫圖片描述
  • redis主備由于作為中心節(jié)點提供緩存,其他節(jié)點都向redis中心節(jié)點取數(shù)據(jù),所以,一次網(wǎng)絡(luò)傳輸即可。(當(dāng)然此處的一次網(wǎng)絡(luò)代價跟組播的代價是不一樣的)但是,隨著訪問量增大,大量的緩存數(shù)據(jù)訪問使得應(yīng)用服務(wù)器和緩存服務(wù)器之間的網(wǎng)絡(luò)I/O消耗越大。(見下圖,同樣三臺應(yīng)用服務(wù)器,redis sentinel作為中心節(jié)點緩存。所謂中心,即所有應(yīng)用服務(wù)器以redis為緩存中心,不再像ehcache集群,緩存是分散存放在應(yīng)用服務(wù)器中,需要互相同步的,任何一臺應(yīng)用服務(wù)器的input,都會經(jīng)過一次copy網(wǎng)絡(luò)傳輸?shù)絩edis,由于redis是中心共享的,那么就可以不用同步的步驟,其他應(yīng)用服務(wù)器需要只需去get取即可。但是,我們會發(fā)現(xiàn)多了N臺服務(wù)器的get的網(wǎng)絡(luò)開銷。)

這里寫圖片描述

提出方案

那么要怎么處理呢?所以兩級緩存的思想誕生了,在redis的方案上做一步優(yōu)化,在緩存到遠(yuǎn)程redis的同時,緩存一份到本地進(jìn)程ehcache(此處的ehcache不用做集群,避免組播帶來的開銷),取緩存的時候會先取本地,沒有會向redis請求,這樣會減少應(yīng)用服務(wù)器<–>緩存服務(wù)器redis之間的網(wǎng)絡(luò)開銷。(見下圖,為了減少get這幾條網(wǎng)絡(luò)傳輸,我們會在每個應(yīng)用服務(wù)器上增加本地的ehcache緩存作為二級緩存,即第一次get到的數(shù)據(jù)存入ehcache,后面output輸出即可從本地ehcache中獲取,不用再訪問redis了,所以就減少了以后get的網(wǎng)絡(luò)開銷。get開銷只要一次,后續(xù)不需要了,除非本地緩存過期需要再get。)
這里寫圖片描述
如果用過j2cache的都應(yīng)該知道,oschina用j2cache這種兩級緩存,實踐證明了該方案是可行的。該篇使用spring+ehcache+redis實現(xiàn)更加簡潔。


方案實施

1、 spring和ehcache集成

主要獲取ehcache作為操作ehcache的對象。

ehcache.xml 代碼如下:


<ehcache updateCheck="false" xmlns:xsi="http://www./2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache./ehcache.xsd">

    <diskStore path="java.io.tmpdir/ehcache"/>

   <!--  默認(rèn)的管理策略 
    maxElementsOnDisk: 在磁盤上緩存的element的最大數(shù)目,默認(rèn)值為0,表示不限制。 
    eternal:設(shè)定緩存的elements是否永遠(yuǎn)不過期。如果為true,則緩存的數(shù)據(jù)始終有效,如果為false那么還要根據(jù)timeToIdleSeconds,timeToLiveSeconds判斷。
    diskPersistent: 是否在磁盤上持久化。指重啟jvm后,數(shù)據(jù)是否有效。默認(rèn)為false。 
    diskExpiryThreadIntervalSeconds:對象檢測線程運行時間間隔。標(biāo)識對象狀態(tài)(過期/持久化)的線程多長時間運行一次。 
    -->
    <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需要保證與其他系統(tǒng)不重復(fù),進(jìn)行端口注冊  -->
    <!-- 若因未注冊,配置了重復(fù)端口,造成權(quán)限緩存數(shù)據(jù)異常,請自行解決  -->
    <cacheManagerPeerProviderFactory
            class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
            properties="peerDiscovery=automatic,
                        multicastGroupAddress=230.0.0.1,
                        multicastGroupPort=4546, timeToLive=1"/>

<!-- replicatePuts=true | false – 當(dāng)一個新元素增加到緩存中的時候是否要復(fù)制到其他的peers. 默認(rèn)是true。 -->
<!-- replicateUpdates=true | false – 當(dāng)一個已經(jīng)在緩存中存在的元素被覆蓋時是否要進(jìn)行復(fù)制。默認(rèn)是true。 -->
<!-- replicateRemovals= true | false – 當(dāng)元素移除的時候是否進(jìn)行復(fù)制。默認(rèn)是true。 -->
<!-- replicateAsynchronously=true | false – 復(fù)制方式是異步的(指定為true時)還是同步的(指定為false時)。默認(rèn)是true。 -->
<!-- replicatePutsViaCopy=true | false – 當(dāng)一個新增元素被拷貝到其他的cache中時是否進(jìn)行復(fù)制指定為true時為復(fù)制,默認(rèn)是true。 -->
<!-- replicateUpdatesViaCopy=true | false – 當(dāng)一個元素被拷貝到其他的cache中時是否進(jìn)行復(fù)制(指定為true時為復(fù)制),默認(rèn)是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配置信息,創(chuàng)建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服務(wù)器地址
redis.ip=127.0.0.1
#Port  
redis.port=6379
#Passord  
#redis.password=123456
#連接超時30000
redis.timeout=30
#最大分配的對象數(shù)  
redis.pool.maxActive=100
#最大能夠保持idel狀態(tài)的對象數(shù)  
redis.pool.maxIdle=30
#當(dāng)池內(nèi)沒有返回對象時,最大等待時間  
redis.pool.maxWait=1000
#當(dāng)調(diào)用borrow Object方法時,是否進(jìn)行有效性檢查
redis.pool.testOnBorrow=true
#當(dāng)調(diào)用return Object方法時,是否進(jìn)行有效性檢查  
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; //默認(rèn)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 使得數(shù)據(jù)不失效
         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轉(zhuǎn)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[]轉(zhuǎn)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、 模擬問題中提到的接口

此處假設(shè)該接口滿足上述條件。

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參數(shù)的地址作為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

實施結(jié)果

我們寫個測試類來模擬下

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 輸出結(jié)果:


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都沒有數(shù)據(jù),訪問接口耗時操作,打印日志:

visit business service findById,id:5

第二次之后的訪問,都會訪問一級緩存ehcache,此時響應(yīng)速度很快。

TEST2 在TEST1結(jié)束后,我們在liveTime的時間內(nèi),也就是redis緩存還未過期再次執(zhí)行,會出現(xiàn)以下結(jié)果


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執(zhí)行完結(jié)束后,ehcache為進(jìn)程間的緩存,自然隨著運行結(jié)束而釋放,所以TEST2出現(xiàn):

Cache L1 (ehcache) :UserServiceImpl/findById/5=null

然而在第二次訪問二級緩存redis,還未到緩存過期時間,所以在redis中找到數(shù)據(jù)(同時數(shù)據(jù)入一級緩存ehcache):

Cache L2 (redis) :UserServiceImpl/findById/5=User [id=5, userName=tony, passWord=**, age=32, sex=M]

此處不會visit….沒有經(jīng)過接口的耗時操作,接下來數(shù)據(jù)都可以在本地緩存ehcache中獲取。


總結(jié)

經(jīng)過demo實踐結(jié)果符合預(yù)期效果,還需更大規(guī)模的測試。遺留了幾個問題,在代碼處的TODO和FIXME中,留給大家一起來思考,一起來探討解決。問題解決和源碼下載:《spring + ehcache + redis兩級緩存實戰(zhàn)篇(2)》

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多