MyBatis二級緩存自定義實現-Redis

package com.zjs.remote.config.redis;


import com.zjs.remote.config.ApplicationContextHolder;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.security.MessageDigest;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * MyBatis二級緩存Redis實現
 */

public class MybatisRedisCache implements Cache
{

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

    /**
     * 統一緩存頭
     */
    private static final String CACHE_NAME = "MyBatis:";
    /**
     * 讀寫鎖:解決緩存擊穿
     */
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    /**
     * 表空間ID:方便後面的緩存清理
     */
    private final String id;
    /**
     * redis服務接口:提供基本的讀寫和清理
     */
    //private static volatile RedisService redisService;

    //private RedisTemplate redisService; //(RedisTemplate) ApplicationContextHolder.getBean("redisTemplate");

    //這裏使用了redis緩存,使用springboot自動注入
    private RedisTemplate redisTemplate;


    /**
     * 信息摘要
     */
    private volatile MessageDigest messageDigest;

    /////////////////////// 解決緩存雪崩,具體範圍根據業務需要設置合理值 //////////////////////////
    /**
     * 緩存最小有效期
     */
    private int minExpireMinutes = 60;
    /**
     * 緩存最大有效期
     */
    private int maxExpireMinutes = 120;

    /**
     * MyBatis給每個表空間初始化的時候要用到
     *
     * @param id 其實就是namespace的值
     */
    public MybatisRedisCache(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
    }

    /**
     * 獲取ID
     *
     * @return 真實值
     */
    @Override
    public String getId() {
        return id;
    }

    /**
     * 創建緩存
     *
     * @param key   其實就是sql語句
     * @param value sql語句查詢結果
     */
    @Override
    public void putObject(Object key, Object value) {
        try {
            String strKey = generateRedisKey(key);
            // 在redis額外維護CacheNamespace創建的key,clear的時候只清理當前CacheNamespace的數據
            getRedisTemplate().opsForHash().put(CACHE_NAME + id, strKey, "1");
            // 有效期 隨機,防止雪崩
            int expireMinutes = RandomUtils.nextInt(minExpireMinutes, maxExpireMinutes);
            logger.info("將查詢結果存儲到cache.key:" + strKey + ",value:" + value);
            getRedisTemplate().opsForValue().set(strKey, value, expireMinutes, TimeUnit.SECONDS);

            logger.debug("Put cache to redis, id={}", strKey);
        } catch (Exception e) {
            logger.error("Redis put failed, key=" + key.toString(), e);
        }
    }

    /**
     * 讀取緩存
     *
     * @param key 其實就是sql語句
     * @return 緩存結果
     */
    @Override
    public Object getObject(Object key) {
        try {
            String strKey = generateRedisKey(key);
            logger.debug("Get cache from redis, id={} key={}", id, strKey);
            return getRedisTemplate().opsForValue().get(strKey);
        } catch (Exception e) {
            logger.error("Redis get failed, fail over to db", e);
            return null;
        }
    }

    /**
     * 刪除緩存
     *
     * @param key 其實就是sql語句
     * @return 結果
     */
    @Override
    public Object removeObject(Object key) {
        try {
            String strKey = generateRedisKey(key);
            getRedisTemplate().delete(strKey);
            logger.debug("Remove cache from redis, id={}", id);
        } catch (Exception e) {
            logger.error("Redis remove failed", e);
        }
        return null;
    }

    /**
     * 緩存清理
     * 應該是根據表空間進行清理
     */
    @Override
    public void clear() {
        try {
            logger.debug("clear cache, id={}", id);
            String hsKey = CACHE_NAME + id;
            // 獲取CacheNamespace所有緩存key
            Map<Object, Object> idMap = getRedisTemplate().opsForHash().entries(hsKey);
            if (!idMap.isEmpty()) {
                Set<Object> keySet = idMap.keySet();
                Set<String> keys = new HashSet<>(keySet.size());
                keySet.forEach(item -> keys.add(item.toString()));
                // 清空CacheNamespace下面所有緩存Key
                getRedisTemplate().delete(keys);
                // 清空CacheNamespace
                getRedisTemplate().delete(hsKey);
            }
        } catch (Exception e) {
            logger.error("clear cache failed", e);
        }
    }

    /**
     * 獲取緩存大小,暫時沒用上
     *
     * @return 長度
     */
    @Override
    public int getSize() {
        return 0;
    }

    /**
     * 獲取讀寫鎖:爲了解決緩存擊穿
     *  mybites 3.2 不生效 
     * @return*/
    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }

    /**
     * 獲取Redis服務接口
     * 使用雙重檢查保證線程安全
     *
     * @return 服務實例
     */
    private RedisTemplate getRedisTemplate() {
        if (redisTemplate == null) {
            synchronized (RedisTemplate.class) {
                if (redisTemplate == null) {
                    redisTemplate = (RedisTemplate) ApplicationContextHolder.getBean("redisTemplate");
                }
            }
        }
        return redisTemplate;
    }

    private String generateRedisKey(Object key) {
        String o = this.id + DigestUtils.sha256Hex(key.toString().getBytes());
        logger.info(o);
        return o;
    }

    public static byte[] append(byte[]... bas) {
        int iLen = 0;
        byte[][] var2 = bas;
        int var3 = bas.length;

        int var4;
        for (var4 = 0; var4 < var3; ++var4) {
            byte[] ba = var2[var4];
            if (ba != null && ba.length > 0) {
                iLen += ba.length;
            }
        }

        byte[] result = new byte[iLen];
        iLen = 0;
        byte[][] var8 = bas;
        var4 = bas.length;

        for (int var9 = 0; var9 < var4; ++var9) {
            byte[] ba = var8[var9];
            if (ba != null && ba.length > 0) {
                System.arraycopy(ba, 0, result, iLen, ba.length);
                iLen += ba.length;
            }
        }

        return result;
    }

    public int getMinExpireMinutes() {
        return minExpireMinutes;
    }

    public MybatisRedisCache setMinExpireMinutes(int minExpireMinutes) {
        this.minExpireMinutes = minExpireMinutes;
        return this;
    }

    public int getMaxExpireMinutes() {
        return maxExpireMinutes;
    }

    public MybatisRedisCache setMaxExpireMinutes(int maxExpireMinutes) {
        this.maxExpireMinutes = maxExpireMinutes;
        return this;
    }
}

 

爲什麼會記錄一下?

  網絡上目前的都有坑。 這是基於我找到的最好的實例,再次修改的。 

 

package com.zjs.remote.config.redis;


import com.zjs.remote.config.ApplicationContextHolder;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.security.MessageDigest;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* MyBatis二級緩存Redis實現
* 重點處理以下幾個問題
* 1、緩存穿透:存儲空值解決,MyBatis框架實現
* 2、緩存擊穿:使用互斥鎖,我們自己實現
* 3、緩存雪崩:緩存有效期設置爲一個隨機範圍,我們自己實現
* 4、讀寫性能:redis key不能過長
*/

public class MybatisRedisCache implements Cache
{

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

/**
* 統一緩存頭
*/
private static final String CACHE_NAME = "MyBatis:";
/**
* 讀寫鎖:解決緩存擊穿
*/
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 表空間ID:方便後面的緩存清理
*/
private final String id;
/**
* redis服務接口:提供基本的讀寫和清理
*/
//private static volatile RedisService redisService;

//private RedisTemplate redisService; //(RedisTemplate) ApplicationContextHolder.getBean("redisTemplate");

//這裏使用了redis緩存,使用springboot自動注入
private RedisTemplate redisTemplate;


/**
* 信息摘要
*/
private volatile MessageDigest messageDigest;

/////////////////////// 解決緩存雪崩,具體範圍根據業務需要設置合理值 //////////////////////////
/**
* 緩存最小有效期
*/
private int minExpireMinutes = 60;
/**
* 緩存最大有效期
*/
private int maxExpireMinutes = 120;

/**
* MyBatis給每個表空間初始化的時候要用到
*
* @param id 其實就是namespace的值
*/
public MybatisRedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}

/**
* 獲取ID
*
* @return 真實值
*/
@Override
public String getId() {
return id;
}

/**
* 創建緩存
*
* @param key 其實就是sql語句
* @param value sql語句查詢結果
*/
@Override
public void putObject(Object key, Object value) {
try {
String strKey = generateRedisKey(key);
// redis額外維護CacheNamespace創建的keyclear的時候只清理當前CacheNamespace的數據
getRedisTemplate().opsForHash().put(CACHE_NAME + id, strKey, "1");
// 有效期 隨機,防止雪崩
int expireMinutes = RandomUtils.nextInt(minExpireMinutes, maxExpireMinutes);
logger.info("將查詢結果存儲到cache.key:" + strKey + ",value:" + value);
getRedisTemplate().opsForValue().set(strKey, value, expireMinutes, TimeUnit.SECONDS);

logger.debug("Put cache to redis, id={}", strKey);
} catch (Exception e) {
logger.error("Redis put failed, key=" + key.toString(), e);
}
}

/**
* 讀取緩存
*
* @param key 其實就是sql語句
* @return 緩存結果
*/
@Override
public Object getObject(Object key) {
try {
String strKey = generateRedisKey(key);
logger.debug("Get cache from redis, id={} key={}", id, strKey);
return getRedisTemplate().opsForValue().get(strKey);
} catch (Exception e) {
logger.error("Redis get failed, fail over to db", e);
return null;
}
}

/**
* 刪除緩存
*
* @param key 其實就是sql語句
* @return 結果
*/
@Override
public Object removeObject(Object key) {
try {
String strKey = generateRedisKey(key);
getRedisTemplate().delete(strKey);
logger.debug("Remove cache from redis, id={}", id);
} catch (Exception e) {
logger.error("Redis remove failed", e);
}
return null;
}

/**
* 緩存清理
* 應該是根據表空間進行清理
*/
@Override
public void clear() {
try {
logger.debug("clear cache, id={}", id);
String hsKey = CACHE_NAME + id;
// 獲取CacheNamespace所有緩存key
Map<Object, Object> idMap = getRedisTemplate().opsForHash().entries(hsKey);
if (!idMap.isEmpty()) {
Set<Object> keySet = idMap.keySet();
Set<String> keys = new HashSet<>(keySet.size());
keySet.forEach(item -> keys.add(item.toString()));
// 清空CacheNamespace下面所有緩存Key
getRedisTemplate().delete(keys);
// 清空CacheNamespace
getRedisTemplate().delete(hsKey);
}
} catch (Exception e) {
logger.error("clear cache failed", e);
}
}

/**
* 獲取緩存大小,暫時沒用上
*
* @return 長度
*/
@Override
public int getSize() {
return 0;
}

/**
* 獲取讀寫鎖:爲了解決緩存擊穿
*
* @return
*/
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}

/**
* 獲取Redis服務接口
* 使用雙重檢查保證線程安全
*
* @return 服務實例
*/
private RedisTemplate getRedisTemplate() {
if (redisTemplate == null) {
synchronized (RedisTemplate.class) {
if (redisTemplate == null) {
redisTemplate = (RedisTemplate) ApplicationContextHolder.getBean("redisTemplate");
}
}
}
return redisTemplate;
}

private String generateRedisKey(Object key) {
String o = this.id + DigestUtils.sha256Hex(key.toString().getBytes());
logger.info(o);
return o;
}

public static byte[] append(byte[]... bas) {
int iLen = 0;
byte[][] var2 = bas;
int var3 = bas.length;

int var4;
for (var4 = 0; var4 < var3; ++var4) {
byte[] ba = var2[var4];
if (ba != null && ba.length > 0) {
iLen += ba.length;
}
}

byte[] result = new byte[iLen];
iLen = 0;
byte[][] var8 = bas;
var4 = bas.length;

for (int var9 = 0; var9 < var4; ++var9) {
byte[] ba = var8[var9];
if (ba != null && ba.length > 0) {
System.arraycopy(ba, 0, result, iLen, ba.length);
iLen += ba.length;
}
}

return result;
}

public int getMinExpireMinutes() {
return minExpireMinutes;
}

public MybatisRedisCache setMinExpireMinutes(int minExpireMinutes) {
this.minExpireMinutes = minExpireMinutes;
return this;
}

public int getMaxExpireMinutes() {
return maxExpireMinutes;
}

public MybatisRedisCache setMaxExpireMinutes(int maxExpireMinutes) {
this.maxExpireMinutes = maxExpireMinutes;
return this;
}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章