高效Redis工具類
一、引言
本篇部落格以redis快取為主。至於什麼是redis快取?還有沒有其它的快取?哪個快取的效能會更好?這裡就不一一做介紹了!(有興趣的可以自己去百度一下)
在日常的開發中,我們或多或少(必須)的會用到快取。為了提高系統性能、提升使用者體驗度,使用者體驗是多麼的重要;這就要求在軟體設計時,不但要注重可靠性、安全性、可擴充套件性以及可維護性等等的一些指標,更要注重使用者的體驗,使用者體驗分很多方面,但是有一點非常重要就是對使用者操作的響應一定要快;怎樣提高使用者訪問的響應速度,這就是擺在架構設計中必須要解決的問題;說到提高服務的響應速度就不得不說快取了;
從系統的層面說,CPU的速度遠遠高於磁碟IO的速度;所以要想提高響應速度,必須減少磁碟IO的操作,但是有很多資訊又是存在資料庫當中的,每次查詢資料庫就是一次IO操作;請求響應時間等於網路響應時間和伺服器響應時間;網路我們控制不了,伺服器響應時間包括CPU計算時間和磁碟IO時間,其中CPU計算時間這個有硬體資源決定的,我們儘量減少演算法的複雜度來減少它,磁碟IO時間,這個時間是非常慢的,應該儘量減少;
當客戶端呼叫某個介面獲取資訊的時候,執行順序1、2、3、4;由於資訊存放在DB中,所以2、3就有一次磁碟IO操作;這個看似非常簡單業務邏輯,但是當你做架構設計的時候往往要考慮最壞的場景,或者當成千上萬的使用者頻繁的呼叫這個介面應該怎麼處理?如果按照上圖這樣的架構處理,這個看似簡單業務的介面會使整個系統變慢,這樣使用者的請求就會長時間得不到響應;這樣的問題怎麼解決那,這時候就該快取登場了;
快取有很多種、也有很多種快取策略。這裡就不一一就探討了,畢竟這篇部落格是講關於的工具類!總之一句話,要想提高系統的效能,儘量減少IO的操作,特別是磁碟IO的操作;使用快取可以有效的避免這種情況;所以在架構設計過程中,涉及到查詢資料庫的時候,應該考慮一下是不是使用快取技術來提高系統的效能,並且降低資料庫的壓力。
二、無知的痛(用get / set方式使用Redis)
在根據某個需求設計業務的時候,比如說購物車:涉及到新增、查詢、修改、刪除。不說這四個具體實現時候的細節,這不就簡單的對資料庫增、刪、改、查嗎?但是,當成千上萬的使用者頻繁訪問的時候,簡單的事情在高併發的情況下就會變的不簡單!這個時候就需要有個東西來代替資料,而且還要比資料庫更快,來應對可能出現的高併發!所以我們想到了(redis)快取,作為一個key / value 存在,很多開發者自然的使用set/get 方式來使用 redis ,實際上這並不是最優化的使用方法。尤其在未啟用VM 情況下,Redis 全部資料需要放入記憶體,節約記憶體尤其重要。 假如一個key-value單元需要最小佔用512位元組,即使只存一個位元組也佔了512位元組
三、hash型別方式使用redis -- 指定返回型別
在日常開發中,我們會把業務中的所有特性封裝成一個物件,再把這個物件存入到redis快取中。在使用redis的時候,通常會把redis常用的方法封裝成一個工具類。然後我們呼叫工具類中的方法,把存入的物件傳到方法中。通常方法會把物件轉換成string存入快取,需要的時候再把string取出來,然後再轉換成物件!大多數的情況就是如此,但是在我實際的開發中傳入的是個和業務有關的物件,返給我的卻是一個object物件(和業務沒有半毛錢關係)。我想要讓返回的物件和業務有關,還得強轉一下!這返回的是單個物件,如果是個list呢?迴圈強轉嗎?那如果是map呢?當然肯定都是能轉的,但是這樣就增加了實際業務中的程式碼量!在我看來多一行於業務無關的程式碼都是罪惡的(縱然它是為了和業務產生關聯),我想要的是 我給你存的是啥!返回的就是啥!別整那些沒用的......
1.根據key和fieid返回hash中指定儲存位置的值,指定返回型別
/**
* 返回hash中指定儲存位置的值
* @param key
* @param fieid
* @param obj
* @param <T>
* @return
*/
public<T> T hget(String key,String fieid,T obj) {
Jedis jedis = null;
try {
logger.info("hget >> key+fieid:{}",key +"+"+ fieid);
jedis = jedisPool.getResource();
String hget = jedis.hget(key, fieid);
ObjectMapper om = new ObjectMapper();
T t =(T) om.readValue(hget, obj.getClass());
return t;
} catch (Exception e) {
logger.error("Jedis hget 異常: " + e.getMessage());
return null;
} finally {
closeRedis(jedis);
}
}
呼叫:
//呼叫hget方法
DemoDTO demoDTO = jedis.hget("key", "fieid", new DemoDTO());
2.hvals()方法,獲取hash中value的集合,指定返回的集合型別
/**
* 獲取hash中value的集合,指定返回的集合型別
* @param key
* @param obj
* @param <T>
* @return
*/
public<T> List<T> hvals(String key,T obj) {
Jedis jedis = null;
try {
logger.info("hvals >> key:{}",key);
jedis = jedisPool.getResource();
List<String> hvals = jedis.hvals(key);
Iterator<String> iterator = hvals.iterator();
List<T> returnList = new ArrayList<>();
ObjectMapper om = new ObjectMapper();
while (iterator.hasNext()) {
String next = iterator.next();
T t =(T) om.readValue(next, obj.getClass());
returnList.add(t);
}
return returnList;
} catch (Exception e) {
logger.error("Jedis hvals fail >> e:{}",e.getMessage());
return null;
} finally {
closeRedis(jedis);
}
}
呼叫:
//呼叫hvals方法
List<DemoDTO> demoListDTOS = jedis.hvals("key", new DemoDTO());
3.以Map的形式返回hash中的儲存和值,指定map中的value型別
/**
* 以Map的形式返回hash中的儲存和值
* @param key
* @param obj
* @param <T>
* @return
*/
public<T> Map<String,T> hgetAll(String key,T obj) {
Jedis jedis = null;
try {
logger.info("hget >> key+obj:{}",key+"+"+obj);
jedis = jedisPool.getResource();
Map<String, String> map = jedis.hgetAll(key);
Map<String, T> returnMap = new HashMap<>();
ObjectMapper om = new ObjectMapper();
for (Map.Entry<String, String> e : map.entrySet()) {
T t =(T) om.readValue(e.getValue(), obj.getClass());
returnMap.put(e.getKey(),t);
}
return returnMap;
} catch (Exception e) {
logger.error("Jedis hget fail >> e:{}" + e.getMessage());
return null;
} finally {
closeRedis(jedis);
}
}
呼叫:
//呼叫hgetAll方法
Map<String,DemoDTO> stringDemoDTOMap = jedis.hgetAll("key", new DemoDTO());
4.關閉jedis連線,將jedis連線歸還redis連線池
/**
* 關閉jedis連線池
* @param jedis
*/
private void closeRedis(Jedis jedis) {
if (jedis != null){
try{
jedis.close();
}catch (Exception e){
logger.error("Jedis關閉異常" + e.getMessage());
}
}
}
closeRedis 釋放資源 防止達到最大連線數出現異常 如 :maxTotal=300
如果每次獲取完redis連線,不歸還redis連線池的話,當超過redis的最大連線數。然後redis就雪崩了!注意!注意!!!
四、結尾
其實指定redis返回型別,沒什麼高大上的!就是用了ObjectMapper類,這個類是Jackson庫的主要類。但是,我百度查過網上很多關於redis的連線工具類,發現卻沒有一個做過這方面的處理!其實這個也不難,在寫業務的時候隨手就能寫出來。我也就是在用redis的時候偶爾蹦出來了這個想法,每都要多那麼兩三行於業務無關的程式碼讓人非常的抓狂!
End...