1. 程式人生 > >高效Redis工具類

高效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位元組

(比如set/get,一個key-value對應一條資料)。這時候就有一個設計模式,可以把key複用,幾個key-value作為一個大的value,大的value再作為一個value,set存入一個key中。這樣同樣512位元組就會存放10-100倍的容量。 這就是為了節約記憶體,建議使用hash型別而不是set/get的方式來使用Redis。

 

三、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...