Cache技术整理

title: Cache技术整理 tags: [Cache] date: 2014-12-25 —– #Cache 目录
    
    
    
    
  • Cache简介
    • |---Cache概述
    • |---简单的Cache解决方案及缺点
    • |---第三方Cache框架
  • Spring Cache
    • |---Spring Cache
    • |---Spring Data Redis
  • 项目中Cache应用
    • |---Spring注解方式
    • |---实现RedisCallback接口
    • |---两种实现方式的优缺点
  • 参考资料



Cache简介

Cache概述

      这里说的Cache不是指CPU和RAM之间的缓存,而是Java应用中间常用的缓存。当不使用缓存时,系统将某些资源或者数据频繁会被使用到的数据或者资源存储在系统外,比如数据库、硬盘文件等,那么每次操作这些数据的时候都从数据库或者硬盘上去获取,速度会很慢,从而造成性能问题。为了解决这个问题,可以将经常用到的资源加载到内存中。每次访问首先查找内存中是否有需要的信息,没有再查数据库或者硬盘文件,这样只要缓存的命中率越高,访问速度越快。

      一个简单的解决方法就是:把这些数据缓存到内存里面,每次操作的时候,先到内存里面找,看有没有这些数据。如果有那么就直接使用;如果没有那么就获取它,并设置到缓存中,下一次访问的时候就可以直接从内存中获取了。从而节省大量的时间,当然,缓存是一种典型的空间换时间的方案

缓存命中率

      即从缓存中读取数据的次数 与 总读取次数的比率,命中率越高越好:

            命中率 = 从缓存中读取次数 / (总读取次数[从缓存中读取次数 + 从慢速设备上读取的次数])

            Miss率 = 没有从缓存中读取的次数 / (总读取次数[从缓存中读取次数 + 从慢速设备上读取的次数])

      这是一个非常重要的监控指标,如果做缓存一定要已这个指标来看缓存是否工作良好。

缓存移除策略(Eviction policy)

      移除策略,即如果缓存满了,从缓存中移除数据的策略。常见的有LFU、LRU、FIFO:

            FIFO(First In First Out):先进先出算法,即先放入缓存的先被移除;

            LRU(Least Recently Used):最久未使用算法,使用时间距离现在最久的那个被移除;

            LFU(Least Frequently Used):最近最少使用算法,一定时间段内使用次数(频率)最少的那个被移除。

TTL(Time To Live )

      存活期,即从缓存中创建时间点开始直到它到期的一个时间段(不管在这个时间段内有没有访问都将过期)

TTI(Time To Idle)

      空闲期,即一个数据多久没被访问将从缓存中移除的时间。

简单的Cache解决方案及缺点

      在Java中最常见的一种实现缓存的方式就是使用static HashMap。

      简单的缓存实现基本步骤是:

            (1)先到缓存里面查找所要查找的想用数据,看看是否存在需要使用的数据

            (2)如果没有找到,那么就创建一个满足要求的数据,然后把这个数据设置回到缓存中,以备下次使用

            (3)如果找到了相应的数据,或者是创建了相应的数据,那就直接使用这个数据。

//简单的缓存实现
public class Cache {

    private static Cache cache = null;

    private static Map cacheMap = new HashMap();

    private Cache() {

    }

    public static synchronized Cache getInstance() {
        if (cache == null) {
            cache = new Cache();
        }
        return cache;
    }

    public String getValue(String key, String value) {
        String cacheValue = cacheMap.get(key);
        if (cacheValue == null || cacheValue.length() == 0) {
            System.out.println("缓存中无数据,将新数据添加到缓存中,值为:" + value);
            cacheMap.put(key, value);
            return value;
        } else {
            System.out.println("缓存中有数据,直接返回value,值为:" + cacheValue);
            return cacheValue;
        }
    }

}

测试:

public class Main {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Cache.getInstance().getValue("Test", "Test Cache");
        }
    }

}

      上述简单Cache的实现,是一个最简单也最不实用的实现,因为无法控制缓存对象的有效性及周期,这样就可能导致内存使用急剧上升,从而导致内存溢出,对于这种简单的内存缓存很难满足企业级应用。

第三方Cache框架

      如上所述,对于需要缓存可控及大量数据的缓存,上述的简单内存缓存已难以派上用场,而要自己实现一个符合要求的缓存机制,单纯靠JDK来实现是比较复杂的。还好现在已经有不少优秀的第三方开源缓存框架来满足需求。

      常见的有Oscache,Ehcache,Jcache,JbossCache等等,一些闻名的缓存框架比较,详见:http://blog.csdn.net/renhui15688/article/details/2312279

Spring Cache

Spring Cache

      从Spring3.1开始,Spring引入了注解Cache支持及Cache抽象,详见CSDN上的一篇博客:http://jinnianshilongnian.iteye.com/blog/2001040,
该篇博客讲的比较全面。包括缓存基础知识和Spring Cache实例。

Spring Data Redis

      Spring-data-redis为spring-data模块中对redis的支持部分,简称为“SDR”,提供了基于jedis客户端API的高度封装以及与spring容器的整合,详见文章:http://shift-alt-ctrl.iteye.com/blog/1886831

项目中Cache应用

      在项目中是使用Redis,对缓存策略进行维护的,Redis的优点:

            1、redis丰富的数据结构,其hash,list,set以及功能丰富的String的支持,对于实际项目中的使用有很大的帮忙;

            2、redis单点性能高效。

      接下来直接通过示例,讲解下项目中使用Redis缓存数据的方式,我觉得这样可能比较直观:

Spring注解方式

                  a)、ocean.cache工程中的cache.xml已经定义了缓存的管理机制,配置参数在sharing工程setting/param/DEV/redis.properties文件中

  
  

    

    
        
        
        
        
        
            
          
     

      
            
        
        
         
    

    
         
        
            
        
    

    
         
    


                  b)、使用缓存的工程依赖ocean.cache包

                  c)、applicationContext.xml中只需定义组件扫描的包




    


                  d)、Cache代码

package com.leijid.rediscache;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

@Component
public class Cache {

    //键值为test_redis_cache + 第一个参数
    @Cacheable(value = "test_redis_cache", key = "#p0")
    public String getValue(String key) {
        System.out.println("缓存中无数据;添加到缓存");
        return "Redis Cache";
    }

}

                  d)、单元测试代码

package com.leijid.rediscache;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath*:/META-INF/spring/*.xml" })
@ActiveProfiles("dev")
public class CacheTest {

    @Autowired
    private Cache cache;

    @Test
    public void testGetValue() {
        for (int i = 0; i < 10; i++) {
            System.out.println(cache.getValue("testRedisCache"));
        }
    }

}

                  e)、测试结果

实现RedisCallback接口

                  a)、因该种方式可以自己指定Redis的DB Index,所以需要添加ocean.data包依赖,该包实现了如何获取Redis的DB Index

                  b)、Cache代码

package com.leijid.rediscache2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

@Component
public class Cache {

    @Autowired
    private RedisTemplate redisTemplate;

    public String getValue(String key, String value) {
        String cacheValue = checkCahce(key);
        if (cacheValue != null && cacheValue.length() != 0) {
            System.out.println("缓存中已有数据,值为:" + cacheValue);
            return cacheValue;
        } else {
            System.out.println("缓存中无数据,添加到缓存中,值为:" + value);
            cache(key, value);
            return value;
        }
    }

    private void cache(final String cacheKey, final String value) {
        redisTemplate.execute(new RedisCallback() {
            @Override
            @SuppressWarnings("unchecked")
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer serializer = (RedisSerializer) redisTemplate.getDefaultSerializer();
                byte[] key = redisTemplate.getStringSerializer().serialize(cacheKey);
                connection.set(key, serializer.serialize(value));
                connection.expire(key, 600); // 失效时间,单位秒
                return true;
            }
        });
    }

    private String checkCahce(final String cacheKey) {
        String resultValue = redisTemplate.execute(new RedisCallback() {
            @Override
            @SuppressWarnings("unchecked")
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] key = redisTemplate.getStringSerializer().serialize(cacheKey);
                byte[] cacheValue = connection.get(key);
                if (cacheValue != null) {
                    RedisSerializer serializer = (RedisSerializer) redisTemplate.getDefaultSerializer();
                    return serializer.deserialize(cacheValue);
                }
                return null;
            }
        });
        return resultValue;
    }
}

                  c)、spring配置文件,配置连接的Redis数据库,数据库配置位置sharing/setting/param/DEV/datasources.properties,根据constructor-arg中的配置的value,拼接出键值datasources.redis.{value值}.url,到配置文件中获取连接的Redis数据库。注:配置的数据库索引不要超出现有的数据库索引,如图配置的DB Index为14




    
        
    
    
        
        
            
        
    

    


                  d)、单元测试代码

package com.leijid.rediscache2;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath*:/META-INF/spring/*.xml" })
@ActiveProfiles("dev")
public class CacheTest {

    @Autowired
    private Cache cache;

    @Test
    public void testGetValue() {
        for (int i = 0; i < 10; i++) {
            cache.getValue("test_redisTemplate", "Test");
        }
    }

}

两种实现方式的优缺点

      谈谈项目使用的这两种方式的优缺点

      第一种方式:

      优点:

            1)、减少代码量

            2)、缓存配置全局控制,便于管理

      缺点:

            1)、不能定制特殊需求的缓存数据

      第二种方式:

      优点:

            1)、可以自己定制Cache策略,控制缓存数据的存放位置,控制缓存的失效时间

      缺点:

            1)、代码量增加,重复代码增加

参考资料

  1. 《浅谈java缓存》 http://aronlulu.iteye.com/blog/651494
  2. 《java中的缓存思想与相应的设计模式》 http://blog.csdn.net/feihong247/article/details/8078434
  3. 《Java实现cache的基本机制》http://blog.csdn.net/michaellufhl/article/details/6203666
  4. 《Spring Cache抽象详解》http://jinnianshilongnian.iteye.com/blog/2001040
  5. 《Spring-data-redis:特性与实例》http://shift-alt-ctrl.iteye.com/blog/1886831

你可能感兴趣的:(redis)