posts - 27,  comments - 0,  trackbacks - 0
          問題引入

          上次我參與某個大型項目的優(yōu)化工作,由于系統(tǒng)要求有比較高的TPS,因此就免不了要使用緩沖。

          該項目中用的緩沖比較多,有MemCache,有Redis,有的還需要提供二級緩沖,也就是說應(yīng)用服務(wù)器這層也可以設(shè)置一些緩沖。

          當(dāng)然去看相關(guān)實現(xiàn)代代碼的時候,大致是下面的樣子。

          1. public void saveSomeObject(SomeObject someObject){ 
          2.  
          3.     MemCacheUtil.put("SomeObject",someObject.getId(),someObject); 
          4.  
          5.     //下面是真實保存對象的代碼 
          6.  
          7.   
          8.  
          9.  
          10. public SomeObject getSomeObject(String id){ 
          11.  
          12.     SomeObject someObject = MemCacheUtil.get("SomeObject",id); 
          13.  
          14.     if(someObject!=null){ 
          15.  
          16.          someObject=//真實的獲取對象 
          17.  
          18.          MemCacheUtil.put("SomeObject",someObject.getId(),someObject); 
          19.  
          20.     } 
          21.  
          22.     return someObject; 
          23.  


          很明顯與緩沖相關(guān)的代碼全部是耦合到原來的業(yè)務(wù)代碼當(dāng)中去的。

          后來由于MemCache表現(xiàn)不夠穩(wěn)定,而且MemCache的功能,也可以由Redis完全進行實現(xiàn),于是就決定從系統(tǒng)中取消MemCache,換成Redis的實現(xiàn)方案,于是就改成如下的樣子:

          1. public void saveSomeObject(SomeObject someObject){ 
          2.  
          3.     RedisUtil.put("SomeObject",someObject.getId(),someObject); 
          4.  
          5.     //下面是真實保存對象的代碼 
          6.  
          7.   
          8.  
          9.  
          10. public SomeObject getSomeObject(String id){ 
          11.  
          12.     SomeObject someObject = RedisUtil.get("SomeObject",id); 
          13.  
          14.     if(someObject!=null){ 
          15.  
          16.          someObject=//真實的獲取對象 <span></span>RedisUtil.put("SomeObject",someObject.getId(),someObject); 
          17.  
          18.     } 
          19.  
          20.     return someObject; 
          21.  


          這一通改下來,開發(fā)人員已經(jīng)暈頭暈?zāi)X的了,后來感覺性能還是不夠高,這個時候,要把一些數(shù)據(jù)增加二級緩沖,也就是說,本地緩沖有就取本地,本地沒有就取遠程緩沖  

          于是,上面的代碼又是一通改,變成下面這個樣子:

          1. public void saveSomeObject(SomeObject someObject){ 
          2.  
          3.     LocalCacheUtil.put("SomeObject",someObject.getId(),someObject); 
          4.  
          5.     RedisUtil.put("SomeObject",someObject.getId(),someObject); 
          6.  
          7.     //下面是真實保存對象的代碼 
          8.  
          9.   
          10.  
          11.  
          12. public SomeObject getSomeObject(String id){ 
          13.  
          14.     SomeObject someObject = LocalCacheUtil.get("SomeObject",id); 
          15.  
          16.     if(someObject!=null){ 
          17.  
          18.         return someObject; 
          19.  
          20.     } 
          21.  
          22.     someObject = RedisUtil.get("SomeObject",id); 
          23.  
          24.     if(someObject!=null){ 
          25.  
          26.          someObject=//真實的獲取對象  
          27.  
          28.          RedisUtil.put("SomeObject",someObject.getId(),someObject); 
          29.  
          30.     } 
          31.  
          32.     return someObject; 
          33.  


          但是這個時候就出現(xiàn)一個問題: 

          由于在某一時刻修改值的只能是某一臺計算機,這個時候,其它的計算機的本地緩沖實際上與遠程及數(shù)據(jù)庫中的數(shù)據(jù)會不一致,這個時候,可以有兩種辦法實現(xiàn),一種是利用Redis的請閱發(fā)布機制進行數(shù)據(jù)同步,這種方式,會保證數(shù)據(jù)能夠被及時同步。

          另外一種方法就是設(shè)置本地緩沖的有效時間比較短,這樣,允許在比較短的時間段內(nèi)出現(xiàn)數(shù)據(jù)不一致的情況。

          不管怎么樣,功能是實現(xiàn)了,程序員小伙伴這個時候已經(jīng)改得眼睛發(fā)黑,手指發(fā)麻,幾乎接近崩潰了。

          很明顯這種實現(xiàn)方式是不好的,于是項目組又提出了改進意見,能否采用注解方式進行標(biāo)注,讓程序員只要聲明就可以?Good idea,于是,又變成了下面的樣子:

          1. @Cache(type="SomeObject",parameter="someObject",key="${someObject.id}"
          2.  
          3. public void saveSomeObject(SomeObject someObject){ 
          4.  
          5.     //下面是真實保存對象的代碼 
          6.  
          7.   
          8.  
          9.  
          10. @Cache("SomeObject",key="${id}"
          11.  
          12. public SomeObject getSomeObject(String id){ 
          13.  
          14.     SomeObject someObject=//真實的獲取 
          15.  
          16.     return someObject; 
          17.  


          這個時候,程序員們的代碼已經(jīng)非常清爽了,里面不再有與緩沖相關(guān)的部分內(nèi)容,但是引入一個新的問題,就是處理注解的代碼怎么寫?需要引入容器,比如:Spring,這些Bean必須被容器所托管,如果直接new一個實例,就沒有辦法用緩沖了。還有一個問題是:程序員的工作量雖然有所節(jié)省,但是還是要對程序代碼有侵入性,需要引入這些注解,如果要增加超越現(xiàn)有注解的功能,還是需要重新寫過這些類,引入其它的注解,修改現(xiàn)有的注解。

          所以,上面是個可以接受的方案,但明顯還不是很好的方案。

          假如有一個程序員火大了,他發(fā)出下面的抱怨:“我只管做我的業(yè)務(wù),放不放緩沖和我有1毛錢關(guān)系么?總因為緩沖的事情讓我改來改去,程序改得亂七八糟不說,我的時間,我的工作進度都影響了誰來管?以后和緩沖相關(guān)的事情別他媽的來煩我!”,作為架構(gòu)師的你,你怎么看?最起碼,我覺得他是說得非常有道理的。我們再返過頭來看看最原始的代碼:

          1. public void saveSomeObject(SomeObject someObject){ 
          2.  
          3.     //下面是真實保存對象的代碼 
          4.  
          5.   
          6.  
          7.  
          8. public SomeObject getSomeObject(String id){ 
          9.  
          10.     SomeObject someObject=//真實的獲取 
          11.  
          12.     return someObject; 
          13.  


          這里是干干凈凈的業(yè)務(wù)代碼,和緩沖沒有一點關(guān)系。后來由于性能方面的要求,需要做緩沖,OK,這一點是事實,但是用什么緩沖或怎么緩沖,與程序員確實是沒有什么關(guān)系的,因此,是不是可以不讓程序員參與,就可以優(yōu)雅的做到添加緩沖功能呢?答案當(dāng)然是肯定的。 

          需求整理

          1. 代碼當(dāng)中,不要體現(xiàn)與緩沖相關(guān)的內(nèi)容,也就是說做不做緩沖及怎么做緩沖不要影響到業(yè)務(wù)代碼
          2. 不管是從容器中取實例還是new實例,都可以同樣的起作用,也就是說可以不必依賴具體的容器

          解決思路:

          放不放緩沖、怎么放緩沖、緩沖有效時間等等,這些內(nèi)容是在運行期發(fā)現(xiàn)存在性能瓶頸,然后提交給程序員來進行優(yōu)化的。為此,我們設(shè)計了一個配置來描述這些緩沖相關(guān)的聲明。

          當(dāng)然,這個配置文件的結(jié)構(gòu),可以根據(jù)自己所采用的緩沖框架來進行相應(yīng)的定義。

          比如:

          1. <redis-caches> 
          2.  
          3.   <redis-cache type="org.tinygroup.redis.test.UserDao"> 
          4.  
          5.      <redis-method method-name="saveUser"> 
          6.  
          7.            <redis-expire value="1000"></redis-expire> 
          8.  
          9.            <redis-string type="user" key="${user.id}" paramter-name="user"><redis-string> 
          10.  
          11.      </redis-method> 
          12.  
          13.   </redis-cache> 
          14.  
          15.   <redis-cache type="org.tinygroup.redis.test.UserDao"> 
          16.  
          17.      <redis-method method-name="getUser"> 
          18.  
          19.            <redis-expire value="1000"></redis-expire> 
          20.  
          21.            <redis-string type="user" key="${id}" ><redis-string> 
          22.  
          23.      </redis-method> 
          24.  
          25.   </redis-cache> 
          26.  
          27. </redis-caches> 


          我們在實際應(yīng)用當(dāng)中,配置比上面的示例更完善,那現(xiàn)在我先講一下上面的兩段配置的含義。

          在UserDao的saveUser的時候,會同步的把User數(shù)據(jù)放到Redis中進行緩沖,緩沖時間為1秒,存放的緩沖數(shù)據(jù)的類型為user,鍵值為${user.id},也就是要保存的用戶的主健。實際進入到Redis的時候,在Redis中的健值是由上面類型與key的共同組成的。

          在調(diào)用UserDao的getUser的時候,會先從緩沖中獲取類型為user,鍵值為${id}的數(shù)據(jù),如果緩沖中在,則取出并返回,如果緩沖中沒有,則從原有業(yè)務(wù)代碼中取出值并放入緩沖,然后返回此對象。

          哇,這個時候,就非常爽了,只要通過聲明就可以做到對緩沖的處理了,但是一個問題就出來了,如何實現(xiàn)上面的需求呢?

          通過配置文件外置,確實做到了對業(yè)務(wù)代碼的0侵入,但是如何為原有業(yè)務(wù)增加緩沖相當(dāng)?shù)臉I(yè)務(wù)邏輯呢?由于需求2要求可以new,也可以從容器中獲取對象實例,因此利用容器AOP解決的跑就被封死了,因此,就得引入字節(jié)碼的方式來進行解決。

          具體實現(xiàn)

          寫一個基于Maven的緩沖代碼處理插件,在編譯后增加此處理插件,根據(jù)配置文件對原有代碼進行掃描并修改其字節(jié)碼以添加緩沖相關(guān)處理邏輯。

          現(xiàn)在只要使用Maven進行compile或install就可以自動添加緩沖相關(guān)的邏輯到class文件中了。

          至此,我們已經(jīng)分析了緩沖代碼直接耦合到代碼中,并分析了其中的缺點,最終演化了注解方式,外置配置方式,并簡要介紹了實現(xiàn)方法。

          具體實現(xiàn),采用的技術(shù)就比較多了,有Maven插件、有Asm、有模板引擎還有Tiny框架的一些基礎(chǔ)工程,如:VFS,F(xiàn)ileResolver等等。

          如果采用Tiny框架,可以直接拿來用,如果不用Tiny框架,可以參照上面的思路做自己的實現(xiàn)。



          歡迎訪問開源技術(shù)社區(qū):http://bbs.tinygroup.org。本例涉及的代碼和框架資料,將會在社區(qū)分享。《自己動手寫框架》成員QQ群:228977971,讓我們一起動手,了解開源框架的奧秘!

          第4波活動:寫書評,送精美開發(fā)圖書!

          posted on 2015-06-23 22:16 柏然 閱讀(61) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          <2015年6月>
          31123456
          78910111213
          14151617181920
          21222324252627
          2829301234
          567891011

          常用鏈接

          留言簿

          隨筆檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 闽清县| 新建县| 衡山县| 托克逊县| 蒙自县| 洪江市| 简阳市| 延庆县| 都昌县| 应城市| 合江县| 绵阳市| 建水县| 遂平县| 娄底市| 玉环县| 安福县| 平泉县| 达拉特旗| 搜索| 门头沟区| 门源| 保山市| 烟台市| 呼玛县| 榆林市| 墨脱县| 海城市| 日土县| 靖宇县| 六盘水市| 通河县| 洛宁县| 开阳县| 黎城县| 利辛县| 三台县| 普定县| 潮州市| 赤壁市| 德州市|