Spring+SpringMVC+MyBatis+easyUI整合進階篇(十四)Redis緩存正確的使用姿式

做者:13
GitHub:https://github.com/ZHENFENG13
版權聲明:本文爲原創文章,未經容許不得轉載。html

簡介

這是一篇關於Redis使用的總結類型文章,會先簡單的談一下緩存的應用場景、緩存的使用邏輯及注意事項,而後是Redis緩存與數據庫間結合以進行系統優化,固然文章的最後也會給出具體的代碼實現,不至於看到文章的你一頭霧水,理論要講,項目代碼也要分享,這是我寫博客的基本出發點。
1mysql

應用場景

Redis能作什麼呢?nginx

這是個好問題,不一樣的人可能會給出不一樣的答案,由於它的應用場景真的不少,做爲一個優秀的nosql數據庫能夠結合其餘產品作不少事情,好比:tomcat集羣的session同步、與nginx和lua結合作限流工具、基於Redis的分佈式鎖實現、分佈式系統惟一主鍵生成策略、秒殺場景中也會看到它、它還可以做爲一個消息隊列.....git

Redis的應用場景不少不少,以上也只是列舉了一部分而已,因爲本文是圍繞個人開源項目perfect-ssm來寫的,因此在本文的場景就是一個緩存中間層,對於讀多寫少的應用場景,咱們常用緩存來進行優化以提升系統性能。github

我曾經寫過一篇《一次線上Mysql數據庫崩潰事故的記錄》的文章,裏面記錄了Web請求是如何絕不留情的摧垮mysql數據庫,進而致使網站應用沒法正常運轉。當時的狀況就是數據庫讀請求太多,事故的主要緣由也是這個,後續的解決方案也就是在項目中添加緩存層,使得熱點數據得以存入緩存,不會重複的去讀取mysql,將大部分請求壓力轉移至Redis緩存中以減輕mysql的負擔。sql

2

接入緩存後的處理邏輯

請求過來後,首先判斷Redis裏面有沒有,有數據則直接返回Redis中的數據給用戶,沒有則查詢數據庫,若是數據庫中也沒有則返回空或者提醒語句便可。數據庫

固然,針對不一樣的操做,對於Redis和mysql的操做也是不一樣的:緩存

添加操做

若是是須要放入緩存的數據,那麼在向mysql數據庫中插入成功後,生成對應的key至,並存入Redis中。tomcat

修改操做

向mysql數據庫中修改爲功後,修改Redis中的數據,可是Redis並無更新語句,因此只能先刪除,再添加完成更新操做。服務器

須要注意的是,考慮到程序對於Redis的操做可能會失敗,這時mysql中的數據已經修改,可是Redis中的數據依然是上一次的數據,致使數據不一致的問題,因此是先操做Redis仍是先操做mysql須要慎重考慮。

刪除操做

與修改操做相同,先刪除數據,再更新緩存,可是一樣會有出現數據不一致問題的可能性須要注意,若是數據庫中的數據刪除了,可是Redis中的數據沒刪除,又會出現業務問題。

查詢操做

首先經過Redis查詢,若是緩存中已經存在數據則直接返回便可,此時就再也不須要經過mysql數據庫來獲取數據,減小對mysql的請求,若是緩存中不存在數據,則依然經過mysql數據庫查詢,查詢到數據後,存入Redis緩存中。

本項目中的代碼是先操做mysql,再操做Redis,有機率會出現上文中提到的數據庫與緩存數據不一致的狀況,因此須要注意,本文的代碼只作參考,用到實際項目中仍是須要根據具體的業務邏輯進行合理的修改。

使用緩存的建議

緩存存儲策略:

能夠緩存的數據的特徵基本上是如下幾點:

  • 熱點數據
  • 實時性要求不高的數據
  • 業務邏輯簡單的數據

至於什麼數據,不一樣的系統、不一樣的項目要求確定不一樣,這裏不作過多討論,只簡單的說一下本身的想法,結合以上的特徵總結以下:

  • 1.首頁數據、分類數據這些數據屬於熱點數據,首頁數據更是熱得發燙,並且這類數據通常實時性不高,不會頻繁的去操做,比較適合放入緩存。
  • 2.詳情數據,好比文章詳情、商品詳情、廣告詳情、我的信息詳情,這些數據庫中單條的的數據能夠以其id生成不一樣的key保存到Redis,操做比較簡單明瞭,在更新或者刪除的時候須要同步更新Redis中的數據,這類數據也適合放入緩存中。
  • 3.列表數據不是特別推薦,除非是實時性和改變頻率真的很低的狀況下,由於列表每每牽涉的數據和操做不少,處理起來比較複雜,若是對實時性要求低的話、或者部分字段更新頻率低的話,能夠換成這部分數據。

緩存存儲策略的制定說難也難,說容易也容易,主要是根據具體的業務場景合理的操做便可,以上只是作了一個簡單的總結。

緩存失效策略:

失效策略必定要作好,血的教訓。

  • 定時刪除

含義:在設置key的過時時間的同時,爲該key建立一個定時器,讓定時器在key的過時時間來臨時,對key進行刪除

優勢:保證內存被儘快釋放

缺點:
若過時key不少,刪除這些key會佔用不少的CPU時間,在CPU時間緊張的狀況下,CPU不能把全部的時間用來作要緊的事兒,還須要去花時間刪除這些key
定時器的建立耗時,若爲每個設置過時時間的key建立一個定時器(將會有大量的定時器產生),性能影響嚴重

  • 惰性刪除

含義:key過時的時候不刪除,每次從數據庫獲取key的時候去檢查是否過時,若過時則刪除,返回null。

優勢:刪除操做只發生在從數據庫取出key的時候發生,並且只刪除當前key,因此對CPU時間的佔用是比較少的,並且此時的刪除是已經到了非作不可的地步(若是此時還不刪除的話,咱們就會獲取到了已通過期的key了)

缺點:若大量的key在超出超時時間後,好久一段時間內,都沒有被獲取過,那麼可能發生內存泄露(無用的垃圾佔用了大量的內存)

  • 按期刪除

含義:每隔一段時間執行一次刪除過時key操做

優勢:
經過限制刪除操做的時長和頻率,來減小刪除操做對CPU時間的佔用--處理"定時刪除"的缺點
按期刪除過時key--處理"惰性刪除"的缺點

缺點
在內存友好方面,不如"定時刪除"
在CPU時間友好方面,不如"惰性刪除"
難點
合理設置刪除操做的執行時長(每次刪除執行多長時間)和執行頻率(每隔多長時間作一次刪除)(這個要根據服務器運行狀況來定了)

參考《Redis設計與實現》

緩存操做順序策略:

在上文中已經講到了操做順序的問題,是先操做mysql呢?仍是先操做Redis呢?這個須要根據本身的業務邏輯來考量,儘可能選擇影響較小且結合友好的方案來作。

代碼實現:

這裏只貼出主要的邏輯代碼,想要完整實現的能夠到代碼倉庫去取。

//添加
@Override
    public int addArticle(Article article) {
        if (articleDao.insertArticle(article) > 0) {
            log.info("insert article success,save article to Redis");
            RedisUtil.put(Constants.ARTICLE_CACHE_KEY + article.getId(), article);
            return 1;
        }
        return 0;
    }

//修改
    @Override
    public int updateArticle(Article article) {
        if (article.getArticleTitle() == null || article.getArticleContent() == null || getTotalArticle(null) > 90 || article.getArticleContent().length() > 50000) {
            return 0;
        }
        if (articleDao.updArticle(article) > 0) {
            log.info("update article success,delete article in Redis and save again");
            RedisUtil.del(Constants.ARTICLE_CACHE_KEY + article.getId());
            RedisUtil.put(Constants.ARTICLE_CACHE_KEY + article.getId(), article);
            return 1;
        }
        return 0;
    }

//刪除
    @Override
    public int deleteArticle(String id) {
        RedisUtil.del(Constants.ARTICLE_CACHE_KEY + id);
        return articleDao.delArticle(id);
    }

//查詢
    @Override
    public Article findById(String id) {
        log.info("get article by id:" + id);
        Article article = (Article) RedisUtil.get(Constants.ARTICLE_CACHE_KEY + id, Article.class);
        if (article != null) {
            log.info("article in Redis");
            return article;
        }
        Article articleFromMysql = articleDao.getArticleById(id);
        if (articleFromMysql != null) {
            log.info("get article from mysql and save article to Redis");
            RedisUtil.put(Constants.ARTICLE_CACHE_KEY + articleFromMysql.getId(), articleFromMysql);
            return articleFromMysql;
        }
        return null;
    }

結語

首發於個人我的博客,新的項目演示地址:perfect-ssm,登陸帳號:admin,密碼:123456

若是有問題或者有一些好的創意,歡迎給我留言,也感謝向我指出項目中存在問題的朋友。

若是你想繼續瞭解該項目能夠查看整個系列文章Spring+SpringMVC+MyBatis+easyUI整合系列文章,也能夠到個人GitHub倉庫或者開源中國代碼倉庫中查看源碼及項目文檔。

相關文章
相關標籤/搜索