码迷,mamicode.com
首页 > 其他好文 > 详细

如何实现一个缓存服务

时间:2016-08-24 06:31:50      阅读:213      评论:0      收藏:0      [点我收藏+]

标签:

  场景:我们对于需要大量计算的场景,希望将结果缓存起来,然后我们一起来实现一个缓存服务。即对于一个相同的输入,它的输出是不变的(也可以短时间不变)

实现说明:这里实现采用GuavaCache+装饰器模式。

首先设计一个缓存服务接口。

public interface CacheableService<I, O> {

    /**
     * 计算服务
     * @param i
     * @return
     * @throws Exception 
     */
    O doService(I i) throws Exception;
}

这里定义了一个缓存服务接口,这里的key和Hashmap的key一样,需要覆写equals和hashcode方法。

技术分享
public class CacheableServiceWrapper<I , O> implements
        CacheableService<I, O>,
        GlobalResource {

    /**
     * 日志
     */
    private final static Logger LOGGER = LoggerFactory
            .getLogger(CacheableServiceWrapper.class);

    /**
     * 缓存大小
     */
    private int MAX_CACHE_SIZE = 20;

    /**
     * 出现异常的时候重试,默认不重试
     */
    private boolean retryOnExp = false;

    /**
     * 重试次数,默认为0,即不重试
     */
    private int retryTimes = 0;

    /**
     * 默认30分钟
     */
    private long expireTimeWhenAccess = 30 * 60;

    /**
     * 缓存
     */
    private LoadingCache<I, Future<O>> cache = null;

    private CacheableService<I, O> cacheableService = null;

    /**
     * Calculate o.
     *
     * @param i the
     * @return the o
     * @throws Exception the exception
     */
    public O doService(final I i) throws Exception {

        Assert.notNull(cacheableService, "请设置好实例");

        int currentTimes = 0;
        while (currentTimes <= retryTimes) {
            try {
                Future<O> oFuture = cache.get(i);
                return oFuture.get();

            } catch (Exception e) {
                if (!retryOnExp) {
                    throw e;
                }
                currentTimes++;
                LoggerUtils.info(LOGGER, "第", currentTimes, "重试,key=", i);
            }
        }
        throw new Exception("任务执行失败");
    }


    /**
     * 提交计算任务
     *
     * @param i
     * @return
     */
    private Future<O> createTask(final I i) {
        Assert.notNull(cacheableService, "请设置好实例");

        LoggerUtils.info(LOGGER, "提交任务,key=", i);
        LoggerUtils.info(LOGGER, "当前cache=", JSON.toJSONString(cache));

        Future<O> resultFuture = THREAD_POOL.submit(new Callable<O>() {

            public O call() throws Exception {
                return cacheableService.doService(i);
            }
        });
        return resultFuture;

    }

    /**
     * 构造函数
     */
    public CacheableServiceWrapper(CacheableService<I, O> cacheableService,
                                            int maxCacheSize, long expireTime) {
        this.cacheableService = cacheableService;
        this.MAX_CACHE_SIZE = maxCacheSize;
        this.expireTimeWhenAccess = expireTime;
        cache = CacheBuilder.newBuilder().maximumSize(MAX_CACHE_SIZE)
                .expireAfterAccess(expireTimeWhenAccess, TimeUnit.SECONDS)
                .build(new CacheLoader<I, Future<O>>() {
                    public Future<O> load(I key) throws ExecutionException {
                        LoggerUtils.warn(LOGGER, "get Element from cacheLoader");
                        return createTask(key);
                    }

                    ;
                });
    }

    /**
     * 构造函数
     */
    public CacheableServiceWrapper(CacheableService<I, O> cacheableService) {
        this.cacheableService = cacheableService;
        cache = CacheBuilder.newBuilder().maximumSize(MAX_CACHE_SIZE)
                .expireAfterAccess(expireTimeWhenAccess, TimeUnit.SECONDS)
                .build(new CacheLoader<I, Future<O>>() {
                    public Future<O> load(I key) throws ExecutionException {
                        LoggerUtils.warn(LOGGER, "get Element from cacheLoader");
                        return createTask(key);
                    }

                    ;
                });
    }

    /**
     * Setter method for property <tt>retryTimes</tt>.
     *
     * @param retryTimes value to be assigned to property retryTimes
     */
    public void setRetryTimes(int retryTimes) {
        this.retryTimes = retryTimes;
    }

    /**
     * Setter method for property <tt>retryOnExp</tt>.
     *
     * @param retryOnExp value to be assigned to property retryOnExp
     */
    public void setRetryOnExp(boolean retryOnExp) {
        this.retryOnExp = retryOnExp;
    }

}
缓存服务装饰器

这个装饰器就是最主要的内容了,实现了对缓存服务的输入和输出的缓存。这里先说明下中间几个重要的属性:

MAX_CACHE_SIZE :缓存空间的大小
retryOnExp :当缓存服务发生异常的时候,是否发起重试
retryTimes :当缓存服务异常需要重试的时候,重新尝试的最大上限。
expireTimeWhenAccess : 缓存失效时间,当key多久没有访问的时候,淘汰数据

然后是doService采用了Guava的缓存机制,当获取缓存为空的时候,会自动去build缓存,这个操作是原子化的,所以不用自己去采用ConcurrentHashmap的putIfAbsent方法去做啦~~~
这里面实现了最主要的逻辑,就是获取缓存,然后去get数据,然后如果异常,根据配置去重试。

好啦现在咱们去测试啦
public class CacheableCalculateServiceTest {

    private CacheableService<String, String> calculateService;

    @Before
    public void before() {
        CacheableServiceWrapper<String, String> wrapper = new CacheableServiceWrapper<String, String>(
            new CacheableService<String, String>() {

                public String doService(String i) throws Exception {
                    Thread.sleep(999);
                    return i + i;
                }
            });
        wrapper.setRetryOnExp(true);
        wrapper.setRetryTimes(2);
        calculateService = wrapper;
    }

    @Test
    public void test() throws Exception {
        MutiThreadRun.init(5).addTaskAndRun(300, new Callable<String>() {

            public String call() throws Exception {
                return calculateService.doService("1");
            }
        });
    }

这里我们为了模拟大量计算的场景,我们将线程暂停了999ms,然后使用5个线程,执行任务999次,结果如下:

2016-08-24 02:00:18:848 com.zhangwei.learning.calculate.CacheableServiceWrapper get Element from cacheLoader
2016-08-24 02:00:20:119 com.zhangwei.learning.calculate.CacheableServiceWrapper 提交任务,key=1
2016-08-24 02:00:20:122 com.zhangwei.learning.calculate.CacheableServiceWrapper 当前cache={}
2016-08-24 02:00:21:106 com.zhangwei.learning.jedis.JedisPoolMonitorTask poolSize=500 borrowed=0 idle=0
2016-08-24 02:00:21:914 com.zhangwei.learning.run.MutiThreadRun 任务执行完毕,执行时间3080ms,共有300个任务,执行异常0次

可以看到,由于key一样,只执行了一次计算,然后剩下299都是从缓存中获取的。

现在我们修改为5个线程,执行300000次。

2016-08-24 02:03:15:013 com.zhangwei.learning.calculate.CacheableServiceWrapper get Element from cacheLoader
2016-08-24 02:03:16:298 com.zhangwei.learning.calculate.CacheableServiceWrapper 提交任务,key=1
2016-08-24 02:03:16:300 com.zhangwei.learning.calculate.CacheableServiceWrapper 当前cache={}
2016-08-24 02:03:17:289 com.zhangwei.learning.jedis.JedisPoolMonitorTask poolSize=500 borrowed=0 idle=0
2016-08-24 02:03:18:312 com.zhangwei.learning.run.MutiThreadRun 任务执行完毕,执行时间3317ms,共有300000个任务,执行异常0次

发现,执行时间没啥区别啦~~~~缓存的效果真是棒棒的~~

PS:我的个人svn地址:http://code.taobao.org/p/learningIT/wiki/index/   有兴趣的可以看下啦~

后面我们再看基于注解去实现缓存~~~

如何实现一个缓存服务

标签:

原文地址:http://www.cnblogs.com/color-my-life/p/5801411.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!