Java對象緩存(1) —— 內存緩存

 

 

開發過程當中會有很是頻繁地查詢某一類對象,尤爲是經過主鍵查詢整個對象的狀況。好比user,對於前端UI來講,極可能展示任何業務列表的時候,都有相關的用戶信息,須要顯示用戶的暱稱、頭像之類的,這是就要把列表中涉及的用戶一個個查出來。前端

傳統的方式是使用數據庫聯合查詢,但若是用戶表很大,和業務表關聯查詢的代價是很高的,並且若是用戶表與業務表不在同一個數據庫實例上,就無法聯合了。另外一種思路就是先查業務表——若是有分頁機制的話,一般結果也就是幾十條,再針對結果集的每一個用戶主鍵,一一查詢對應的用戶信息。後者的好處是查詢壓力是可控的,不至於讓數據庫爆掉;缺點在於對數據庫的查詢請求仍是過於頻繁。java

在這種狀況下,若是用戶信息不常常變更,就能夠將其緩存起來,每次從緩存中獲取數據,從而減輕數據庫壓力。Java中最簡單的內存緩存實現就是用HashMap,以數據庫主鍵爲key,整個對象序列化之後的字符串做爲value。但HashMap不是線程安全的,併發狀況下,可能出現意想不到的錯誤,因此應該是用concurrent·包中的ConcurrentHashMap類實現。代碼示例以下:sql

package com.nuanxinli.cache;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;

import com.nuanxinli.bo.instance.User;

@IocBean
public class UserCache {
	private ConcurrentMap<String, User> userMap = new ConcurrentHashMap<String, User>();
	@Inject("refer:system_dao")
	private Dao dao;

	public User get(String username) {
		User user = userMap.get(username);
		if (user == null) {
			user = dao.fetch(User.class, username);
			userMap.put(username, user);
		}
		return user;
	}

	public void updateOneUser(User user) {
		userMap.put(user.getUsername(),user);		
	}

}

從代碼邏輯能夠看出,調用get方法時,首先從map裏查找,若是map中沒有,再從數據庫裏取,而且把結果加入到緩存map中,下次在使用便可直接從map中取到。另外,提供了一個updateOneUser方法,以便在用戶信息發生變化時更新緩存。下面是使用UserCache的例子數據庫

package com.nuanxinli.logic.livecast;

import java.util.List;

import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.QueryResult;
import org.nutz.dao.pager.Pager;
import org.nutz.dao.sql.Criteria;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;

import com.nuanxinli.application.ImHttpException;
import com.nuanxinli.bo.instance.livecast.LiveComment;
import com.nuanxinli.cache.UserCache;
import com.nuanxinli.util.StrUtil;

@IocBean
public class LiveCommentLogic {

	@Inject("refer:gold_dao")
	private Dao goldDao;
	@Inject
	private UserCache userCache;

	public QueryResult getList(Integer liveId, String type, Integer pageNum, Integer pageSize) {
        //查詢業務對象列表
		Criteria cri = Cnd.cri();
		if (StrUtil.isNotNullOrBlank(type)) {
			cri.where().and("type", "=", type);
		}
		cri.where().and("is_deleted", "=", 0).and("live_id", "=", liveId);
		Pager pager = goldDao.createPager(pageNum, pageSize);
		List<LiveComment> list = goldDao.query(LiveComment.class, cri, pager);
		pager.setRecordCount(goldDao.count(LiveComment.class, cri));
        //遍歷列表,把其中的用戶信息補全
		for (LiveComment liveComment : list) {
			liveComment.setCreateUser(userCache.get(liveComment.getCreator()));
		}
		return new QueryResult(list, pager);
	}

}

這裏的代碼使用了Nutz的ioc、dao和Json序列化框架。緩存

這個緩存方法固然是簡單粗暴的,不少問題都沒有考慮到,好比:安全

  1. ConcurrentMap的內存使用效率不高,一旦緩存用戶量大,會撐爆內存。好在只有被查詢到的用戶纔會被緩存,而大部分應用活躍用戶數都遠小於總用戶數,對內存的壓力不算太大。
  2. Java應用服務器一旦重啓,緩存所有丟失
  3. 若是應用服務器分佈式部署,相互之間緩存沒法共享

後兩個問題,就須要引入獨立的集中緩存方案了,後面繼續總結。服務器

相關文章
相關標籤/搜索