項目中redis+EhCache實現一二級緩存

主要幾個問題點

1. 什麼作爲緩存中的key?

以類名+方法名+業務id 做爲緩存中key。

String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName() + "-id:" + id;

2. 細節-如何保證添加到一級和二級緩存的數據有效時間相同

例如代碼流程:

 1. 先添加二級redis緩存 

 2. 再添加一級EhCache緩存

解決方法1:一級緩存的有效期時間減去二級緩存代碼的執行時間 = 一級緩存時間有效期。   不靠譜

解決方法2:如有效期爲2小時,則提前10分鐘去刷新重置緩存。   靠譜

流程圖

代碼

EhCacheUtils緩存工具類

代碼很簡單,根據流程圖理解差不多就能完成,所以有些代碼沒copy出來。

/**
 * 功能說明:
 * 功能作者:
 * 創建日期:
 * 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com
 */
package com.itmayiedu.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.stereotype.Component;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

/**
 * 功能說明: <br>
 * 創建作者:每特教育-餘勝軍<br>
 * 創建時間:2018年8月4日 下午3:31:42<br>
 * 教育機構:每特教育|螞蟻課堂<br>
 * 版權說明:上海每特教育科技有限公司版權所有<br>
 * 官方網站:www.itmayiedu.com|www.meitedu.com<br>
 * 聯繫方式:qq644064779<br>
 * 注意:本內容有每特教育學員共同研發,請尊重原創版權
 */
@Component
public class EhCacheUtils {

	// @Autowired
	// private CacheManager cacheManager;
	@Autowired
	private EhCacheCacheManager ehCacheCacheManager;

	// 添加本地緩存 (相同的key 會直接覆蓋)
	public void put(String cacheName, String key, Object value) {
		Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
		Element element = new Element(key, value);
		cache.put(element);
	}

	// 獲取本地緩存
	public Object get(String cacheName, String key) {
		Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
		Element element = cache.get(key);
		return element == null ? null : element.getObjectValue();
	}

	public void remove(String cacheName, String key) {
		Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
		cache.remove(key);
	}

}

RedisService工具類

/**
 * 功能說明:
 * 功能作者:
 * 創建日期:
 * 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com
 */
package com.itmayiedu.service;

import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

/**
 * 功能說明: <br>
 * 創建作者:每特教育-餘勝軍<br>
 * 創建時間:2018年8月1日 下午4:07:32<br>
 * 教育機構:每特教育|螞蟻課堂<br>
 * 版權說明:上海每特教育科技有限公司版權所有<br>
 * 官方網站:www.itmayiedu.com|www.meitedu.com<br>
 * 聯繫方式:qq644064779<br>
 * 注意:本內容有每特教育學員共同研發,請尊重原創版權
 */

@Component
public class RedisService {

	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	// public void set(String key, Object object, Long time) {
	// stringRedisTemplate.opsForValue();
	// // 存放String 類型
	// if (object instanceof String) {
	// setString(key, object);
	// }
	// // 存放 set類型
	// if (object instanceof Set) {
	// setSet(key, object);
	// }
	// // 設置有效期 以秒爲單位
	// stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
	// }
	//
	public void setString(String key, Object object) {
		// 開啓事務權限
		stringRedisTemplate.setEnableTransactionSupport(true);
		try {
			// 開啓事務 begin
			stringRedisTemplate.multi();
			String value = (String) object;
			stringRedisTemplate.opsForValue().set(key, value);
			System.out.println("存入完畢,馬上開始提交redis事務");
			// 提交事務
			stringRedisTemplate.exec();
		} catch (Exception e) {
			// 需要回滾事務
			stringRedisTemplate.discard();
		}
	}

	public void setSet(String key, Object object) {
		Set<String> value = (Set<String>) object;
		for (String oj : value) {
			stringRedisTemplate.opsForSet().add(key, oj);
		}
	}

	public String getString(String key) {
		return stringRedisTemplate.opsForValue().get(key);
	}

}

業務邏輯層

以下代碼可寫成註解形式aop,重複使用。

/**
 * 功能說明:
 * 功能作者:
 * 創建日期:
 * 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com
 */
package com.itmayiedu.service;

import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;
import com.itmayiedu.entity.Users;
import com.itmayiedu.mapper.UserMapper;

/**
 * 功能說明: <br>
 * 創建作者:每特教育-餘勝軍<br>
 * 創建時間:2018年8月4日 下午4:14:35<br>
 * 教育機構:每特教育|螞蟻課堂<br>
 * 版權說明:上海每特教育科技有限公司版權所有<br>
 * 官方網站:www.itmayiedu.com|www.meitedu.com<br>
 * 聯繫方式:qq644064779<br>
 * 注意:本內容有每特教育學員共同研發,請尊重原創版權
 */
@Service
public class UserService {
	@Autowired
	private EhCacheUtils ehCacheUtils;
	private static final String CACHENAME_USERCACHE = "userCache";
	@Autowired
	private RedisService redisService;
	@Autowired
	private UserMapper userMapper;

	public Users getUser(Long id) {
        // 根據規則獲取key
		String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
				+ "-id:" + id;
		// 1.先查找一級緩存(本地緩存),如果本地緩存有數據直接返回
		Users ehUser = (Users) ehCacheUtils.get(CACHENAME_USERCACHE, key);
		if (ehUser != null) {
			System.out.println("使用key:" + key + ",查詢一級緩存 ehCache 獲取到ehUser:" + JSONObject.toJSONString(ehUser));
			return ehUser;
		}
		// 2. 如果本地緩存沒有該數據,直接查詢二級緩存(redis)
		String redisUserJson = redisService.getString(key);
		if (!StringUtils.isEmpty(redisUserJson)) {
			// 將json 轉換爲對象(如果二級緩存redis中有數據直接返回二級緩存)
			JSONObject jsonObject = new JSONObject();
			Users user = jsonObject.parseObject(redisUserJson, Users.class);
			// 更新一級緩存
			ehCacheUtils.put(CACHENAME_USERCACHE, key, user);
			System.out.println("使用key:" + key + ",查詢二級緩存 redis 獲取到ehUser:" + JSONObject.toJSONString(user));
			return user;
		}
		// 3. 如果二級緩存redis中也沒有數據,查詢數據庫
		Users user = userMapper.getUser(id);
		if (user == null) {
			return null;
		}

                // 問題:如何保證二級緩存和一級緩存的失效失效一致? 一級緩存的有效時間減去二級緩存代碼執行時間。
		// 更新二級緩存
		String userJson = JSONObject.toJSONString(user);
		redisService.setString(key, userJson);
		// 更新和一級緩存
                ehCacheUtils.put(CACHENAME_USERCACHE, key, user);
		System.out.println("使用key:" + key + ",一級緩存和二級都沒有數據,直接查詢db" + userJson);
		return user;
	}

}

具體的demo可查看:https://pan.baidu.com/s/1pqc7Hisv6XRxzA0X65DWkw

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