项目中cache应用实践

来源:互联网 发布:手机淘宝能微信支付吗 编辑:程序博客网 时间:2024/05/21 21:27

项目中缓存应用场景分析

  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 0
原创粉丝点击