项目中cache应用实践

项目中缓存应用场景分析

  1. 以前项目采用hibernate的orm模型,memcached来做数据层的二级缓存,效果非常的好。现在互联网项目为充分利用数据库特征都采用mybatis来做orm框架。
  2. 项目中我们还是有一些缓存使用场景,spring提供了基于service方式拦截的缓存解决方案。org.springframework.cache.cache接口提供一些方法,spring还提供相应的实现类。详情可参考http://jinnianshilongnian.iteye.com/blog/2001040
  3. 为结合公司项目,决定自己来设计一套自己cache支持组件,采用redis来作为缓存工具,借鉴spring cache模型,使用aop methodInterceptor来实现service方法的缓存机制。
  4. spring cache设计是可crud操作维护数据缓存。我设计cache只是为减少数据查询,提高响应速度。使用场景适合数据不需要及时准备的展示。

定义CacheManager接口及方法

/**
 * 缓存管理接口
 * 
 * @author my
 * @Date 2016年8月7日 下午4:11:21
 */
public interface CacheManager {
    /**
     * 得到缓存记录
     * @author andrexu
     * @param key
     * @return
     */
    public Serializable get(String key);
    /**
     * 写入一条记录到缓存中
     * @author andrexu
     * @param key   缓存key
     * @param result    缓存内容
     * @param timeToIdleSeconds 过期时间(单位秒)
     */
    public boolean put(String key, Serializable result, int timeToIdleSeconds);
    /**
     * 写入一条记录到缓存中
     * @author andrexu
     * @param key       缓存key
     * @param result    缓存内容
     * @param idleDate  过期日期
     */
    public boolean put(String key, Serializable result, Date idleDate);
    /**
     * 删除一条缓存
     * @author andrexu
     * @param key
     */
    public void remove(String key);
    /**
     * 得到缓存数量
     * @author andrexu
     * @return
     */
    public int getSize();
    /**
     * 清空缓存
     * @author andrexu
     */
    public void clear();
}

主要有get,put,rmove方法,put必须设置失效时间,失效时间点。

redis实现类RedisStrCacheManagerImpl

  1. redis比mecached作为cache优点,就不在概述。
  2. key采用String序列化。value选择json序列化相比jdk序列化速度快,体积小,在redisClient软件可以浏览缓存数据。
@Service("redisStrCacheManagerImpl")
public class RedisStrCacheManagerImpl implements CacheManager {

    private final Logger logger = LoggerFactory.getLogger(RedisStrCacheManagerImpl.class);

    public static final String PREFIX_KEY = "calm:cache:";
    private RedisSerializer<String> keySerializer = new StringRedisSerializer();
   // private RedisSerializer<Object> valueSerializer = new JdkSerializationRedisSerializer();
    private RedisSerializer<Object> valueSerializer =new Jackson2JsonRedisSerializer<Object>(Object.class);

    @Resource(name = "redisTemplate")
    private RedisTemplate<Serializable, Object> redisTemplate;

    @Override
    public Serializable get(String cachekey) {
        try {
            final String cachekey2 = PREFIX_KEY + cachekey;
            Object valueObject = redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    byte[] key = keySerializer.serialize(cachekey2);
                    if (connection.exists(key)) {
                        byte[] value = connection.get(key);
                        return valueSerializer.deserialize(value);
                    }
                    return null;
                }
            });
            if (valueObject == null) {
                return null;
            }
            return (Serializable) valueObject;
        } catch (Exception e) {
            logger.error("RedisStrCacheManagerImpl:" + e.getMessage());
        }
        return null;
    }

    @Override
    public boolean put(String key, final Serializable result, final int timeToIdleSeconds) {
        try {
            final String cachekey = PREFIX_KEY + key;
            redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    byte[] keyArray = keySerializer.serialize(cachekey);
                    connection.set(keyArray, valueSerializer.serialize(result));
                    //api里失效时间传 毫秒。
                    connection.pExpire(keyArray, TimeoutUtils.toMillis(timeToIdleSeconds, TimeUnit.SECONDS));
                    return null;
                }
            });
        } catch (Exception e) {
            logger.error("RedisStrCacheManagerImpl put:" + e.getMessage());
        }
        return true;
    }

    @Override
    public boolean put(String key, final Serializable result, Date idleDate) {
        try {
            Date currentDate = new Date();
            final long expireTime = idleDate.getTime() - currentDate.getTime();
            final String cachekey = PREFIX_KEY + key;
            redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    byte[] keyArray = keySerializer.serialize(cachekey);
                    connection.set(keyArray, valueSerializer.serialize(result));
                    connection.pExpire(keyArray, expireTime);
                    return null;
                }
            });
        } catch (Exception e) {
            logger.error("RedisStrCacheManagerImpl put:" + e.getMessage());
        }
        return true;
    }

    @Override
    public void remove(String key) {
        final String cachekey = PREFIX_KEY + key;
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] keyArray = keySerializer.serialize(cachekey);
                connection.del(keyArray);
                return null;
            }
        });

    }

    @Override
    public int getSize() {
        if (true) {
            throw new RuntimeException("not the method");
        }
        return 0;
    }

    @Override
    public void clear() {
        throw new RuntimeException("not the method");

    }

在redisManager浏览cache数据:
这里写图片描述

MethodInterceptor详解

  1. 在controller调用service方式时,采用了MethodInterceptor方法来判断是否使用cache,查询cache。
  2. 根据Service方式上MethodCache注解来判断是否使用cache,及cache失效策略。
  3. 关注cache的key生成策略,参考了spring的DefaultKeyGenerator类,这里生成策略是className.methodName.arguments[0].arguments[1]的拼接字符串,然后再用Base64转码,参考代码方法getCacheKey
package com.calm.b.common.cache.methodcache;

import com.calm.b.common.cache.CacheManager;
import com.calm.b.common.cache.CacheUtils;
import com.calm.b.common.cache.anno.MethodCache;
import com.calm.b.util.Base64Utils;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.Resource;
import java.io.Serializable;
import java.lang.reflect.Method;


/**
 *
 * @author my
 * @Date 2016年8月7日 下午4:09:35
 */
public class MethodCacheInterceptor implements MethodInterceptor {
    private final Logger logger= LoggerFactory.getLogger(MethodCacheInterceptor.class);
    @Resource(name="redisStrCacheManagerImpl")
    private CacheManager memcachedManager;


    @Value("${enable.method.cache}")
    private String enableMethodCache;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        String realClassName = StringUtils.substringBefore(invocation.getThis().toString(), "@");
        Class<?> clazz = Class.forName(realClassName);
        Method invocationMethod = invocation.getMethod();
        Method method = clazz.getMethod(invocationMethod.getName(), invocationMethod.getParameterTypes());
        if (method.getReturnType().getName().equals("void")) {
            return invocation.proceed();
        }

        MethodCache methodCache = method.getAnnotation(MethodCache.class);
        if (methodCache == null||methodCache.timeToIdleSeconds()<=0||!StringUtils.equals(enableMethodCache,"true")) {
            return invocation.proceed();
        }

        String targetName = clazz.getName();
        String methodName = method.getName();
        /**
         * 根据className,methodName,参数,及忽略参数,生产缓存key
         */
        String cacheKey = getCacheKey(targetName, methodName, invocation.getArguments(), methodCache.ignoreParams());

        Serializable cacheResult = memcachedManager.get(cacheKey);
        if (cacheResult == null) {
            cacheResult = (Serializable) invocation.proceed();
            memcachedManager.put(cacheKey, cacheResult,
                    CacheUtils.calculateExpireDate(methodCache.idleTime(), methodCache.timeToIdleSeconds()));
        }
        return cacheResult;
    }

    /**
     * key生成机制:className.methodName.arguments[0].arguments[1]. ... 再进行转码 若argument为领域对象,请重写其hashCode方法
     *
     * @param targetName
     * @param methodName
     * @param arguments
     * @param ignoreParams
     * @return
     */
    private String getCacheKey(String targetName, String methodName, Object[] arguments, boolean ignoreParams) {
        StringBuffer sb = new StringBuffer();
        sb.append(targetName).append(".").append(methodName);
        if (!ignoreParams) {
            if ((arguments != null) && (arguments.length != 0)) {
                for (int i = 0; i < arguments.length; i++) {
                    sb.append(".").append(arguments[i]);
                }
            }
        }
        return Base64Utils.encoding(sb.toString());
    }

}

spring的proxy配置如下,采用spring proxy配置模式。

<!-- redis method cache component-->
    <bean id="methodCacheInterceptor" lazy-init="false"
          class="com.calm.b.common.cache.methodcache.MethodCacheInterceptor"/>

    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames">
            <value>*CacheService</value>
        </property>
        <property name="interceptorNames">
            <list>
                <value>methodCacheInterceptor</value>
            </list>
        </property>
    </bean>

项目service方法使用:

@Override
  @MethodCache(timeToIdleSeconds = 300)
  public List<AuthorizedLogVo> findAuthorizedLogVos(Long tradeId){
    // ignore business logic code
  }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值