做者:13
GitHub:https://github.com/ZHENFENG13
版權聲明:本文爲原創文章,未經容許不得轉載。html
這是一篇關於Redis使用的總結類型文章,會先簡單的談一下緩存的應用場景、緩存的使用邏輯及注意事項,而後是Redis緩存與數據庫間結合以進行系統優化,固然文章的最後也會給出具體的代碼實現,不至於看到文章的你一頭霧水,理論要講,項目代碼也要分享,這是我寫博客的基本出發點。
mysql
Redis能作什麼呢?nginx
這是個好問題,不一樣的人可能會給出不一樣的答案,由於它的應用場景真的不少,做爲一個優秀的nosql數據庫能夠結合其餘產品作不少事情,好比:tomcat集羣的session同步、與nginx和lua結合作限流工具、基於Redis的分佈式鎖實現、分佈式系統惟一主鍵生成策略、秒殺場景中也會看到它、它還可以做爲一個消息隊列.....git
Redis的應用場景不少不少,以上也只是列舉了一部分而已,因爲本文是圍繞個人開源項目perfect-ssm來寫的,因此在本文的場景就是一個緩存中間層,對於讀多寫少的應用場景,咱們常用緩存來進行優化以提升系統性能。github
我曾經寫過一篇《一次線上Mysql數據庫崩潰事故的記錄》的文章,裏面記錄了Web請求是如何絕不留情的摧垮mysql數據庫,進而致使網站應用沒法正常運轉。當時的狀況就是數據庫讀請求太多,事故的主要緣由也是這個,後續的解決方案也就是在項目中添加緩存層,使得熱點數據得以存入緩存,不會重複的去讀取mysql,將大部分請求壓力轉移至Redis緩存中以減輕mysql的負擔。sql
請求過來後,首先判斷Redis裏面有沒有,有數據則直接返回Redis中的數據給用戶,沒有則查詢數據庫,若是數據庫中也沒有則返回空或者提醒語句便可。數據庫
固然,針對不一樣的操做,對於Redis和mysql的操做也是不一樣的:緩存
若是是須要放入緩存的數據,那麼在向mysql數據庫中插入成功後,生成對應的key至,並存入Redis中。tomcat
向mysql數據庫中修改爲功後,修改Redis中的數據,可是Redis並無更新語句,因此只能先刪除,再添加完成更新操做。服務器
須要注意的是,考慮到程序對於Redis的操做可能會失敗,這時mysql中的數據已經修改,可是Redis中的數據依然是上一次的數據,致使數據不一致的問題,因此是先操做Redis仍是先操做mysql須要慎重考慮。
與修改操做相同,先刪除數據,再更新緩存,可是一樣會有出現數據不一致問題的可能性須要注意,若是數據庫中的數據刪除了,可是Redis中的數據沒刪除,又會出現業務問題。
首先經過Redis查詢,若是緩存中已經存在數據則直接返回便可,此時就再也不須要經過mysql數據庫來獲取數據,減小對mysql的請求,若是緩存中不存在數據,則依然經過mysql數據庫查詢,查詢到數據後,存入Redis緩存中。
本項目中的代碼是先操做mysql,再操做Redis,有機率會出現上文中提到的數據庫與緩存數據不一致的狀況,因此須要注意,本文的代碼只作參考,用到實際項目中仍是須要根據具體的業務邏輯進行合理的修改。
能夠緩存的數據的特徵基本上是如下幾點:
至於什麼數據,不一樣的系統、不一樣的項目要求確定不一樣,這裏不作過多討論,只簡單的說一下本身的想法,結合以上的特徵總結以下:
緩存存儲策略的制定說難也難,說容易也容易,主要是根據具體的業務場景合理的操做便可,以上只是作了一個簡單的總結。
失效策略必定要作好,血的教訓。
含義:在設置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倉庫或者開源中國代碼倉庫中查看源碼及項目文檔。