天天看點

緩存相關代碼的演變問題引入需求整了解決思路:具體實作

上次我參與某個大型項目的優化工作,由于系統要求有比較高的tps,是以就免不了要使用緩沖。

該項目中用的緩沖比較多,有memcache,有redis,有的還需要提供二級緩沖,也就是說應用伺服器這層也可以設定一些緩沖。

當然去看相關實作代代碼的時候,大緻是下面的樣子。

<a href="http://my.oschina.net/tinyframework/blog/322913#">?</a>

1

2

3

4

5

6

7

8

9

10

11

12

13

<code>public</code> <code>void</code> <code>savesomeobject(someobject someobject){</code>

<code>    </code><code>memcacheutil.put(</code><code>"someobject"</code><code>,someobject.getid(),someobject);</code>

<code>    </code><code>//下面是真實儲存對象的代碼</code>

<code>}</code>

<code>public</code> <code>someobject getsomeobject(string id){</code>

<code>    </code><code>someobject someobject = memcacheutil.get(</code><code>"someobject"</code><code>,id);</code>

<code>    </code><code>if</code><code>(someobject!=</code><code>null</code><code>){</code>

<code>         </code><code>someobject=</code><code>//真實的擷取對象</code>

<code>         </code><code>memcacheutil.put(</code><code>"someobject"</code><code>,someobject.getid(),someobject);</code>

<code>    </code><code>}</code>

<code>    </code><code>return</code> <code>someobject;</code>

很明顯與緩沖相關的代碼全部是耦合到原來的業務代碼當中去的。

後來由于memcache表現不夠穩定,而且memcache的功能,也可以由redis完全進行實作,于是就決定從系統中取消memcache,換成redis的實作方案,于是就改成如下的樣子:

<code>    </code><code>redisutil.put(</code><code>"someobject"</code><code>,someobject.getid(),someobject);</code>

<code>    </code><code>someobject someobject = redisutil.get(</code><code>"someobject"</code><code>,id);</code>

<code>         </code><code>someobject=</code><code>//真實的擷取對象 &lt;span&gt;&lt;/span&gt;redisutil.put("someobject",someobject.getid(),someobject);</code>

這一通改下來,開發人員已經暈頭暈腦的了,後來感覺性能還是不夠高,這個時候,要把一些資料增加二級緩沖,也就是說,本地緩沖有就取本地,本地沒有就取遠端緩沖 

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

14

15

16

17

18

<code>    </code><code>localcacheutil.put(</code><code>"someobject"</code><code>,someobject.getid(),someobject);</code>

<code>   </code><code>someobject someobject = localcacheutil.get(</code><code>"someobject"</code><code>,id);</code>

<code>        </code><code>return</code> <code>someobject;</code>

<code>    </code><code>someobject = redisutil.get(</code><code>"someobject"</code><code>,id);</code>

<code>         </code><code>redisutil.put(</code><code>"someobject"</code><code>,someobject.getid(),someobject);</code>

但是這個時候就出現一個問題:

由于在某一時刻修改值的隻能是某一台計算機,這個時候,其它的計算機的本地緩沖實際上與遠端及資料庫中的資料會不一緻,這個時候,可以有兩種辦法實作,一種是利用redis的請閱釋出機制進行資料同步,這種方式,會保證資料能夠被及時同步。

另外一種方法就是設定本地緩沖的有效時間比較短,這樣,允許在比較短的時間段内出現資料不一緻的情況。

不管怎麼樣,功能是實作了,程式員小夥伴這個時候已經改得眼睛發黑,手指發麻,幾乎接近崩潰了。

很明顯這種實作方式是不好的,于是項目組又提出了改進意見,能否采用注解方式進行标注,讓程式員隻要聲明就可以?good idea,于是,又變成了下面的樣子:

<code>@cache</code><code>(type=</code><code>"someobject"</code><code>,parameter=</code><code>"someobject"</code><code>,key=</code><code>"${someobject.id}"</code><code>)</code>

<code>@cache</code><code>(</code><code>"someobject"</code><code>,key=</code><code>"${id}"</code><code>)</code>

<code>    </code><code>someobject someobject=</code><code>//真實的擷取</code>

這個時候,程式員們的代碼已經非常清爽了,裡面不再有與緩沖相關的部分内容,但是引入一個新的問題,就是處理注解的代碼怎麼寫?需要引入容器,比如:spring,這些bean必須被容器所托管,如果直接new一個執行個體,就沒有辦法用緩沖了。還有一個問題是:程式員的工作量雖然有所節省,但是還是要對程式代碼有侵入性,需要引入這些注解,如果要增加超越現有注解的功能,還是需要重新寫過這些類,引入其它的注解,修改現有的注解。

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

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

這裡是幹幹淨淨的業務代碼,和緩沖沒有一點關系。後來由于性能方面的要求,需要做緩沖,ok,這一點是事實,但是用什麼緩沖或怎麼緩沖,與程式員确實是沒有什麼關系的,是以,是不是可以不讓程式員參與,就可以優雅的做到添加緩沖功能呢?答案當然是肯定的。

代碼當中,不要展現與緩沖相關的内容,也就是說做不做緩沖及怎麼做緩沖不要影響到業務代碼

不管是從容器中取執行個體還是new執行個體,都可以同樣的起作用,也就是說可以不必依賴具體的容器

放不放緩沖、怎麼放緩沖、緩沖有效時間等等,這些内容是在運作期發現存在性能瓶頸,然後送出給程式員來進行優化的。為此,我們設計了一個配置來描述這些緩沖相關的聲明。

當然,這個配置檔案的結構,可以根據自己所采用的緩沖架構來進行相應的定義。

比如:

<code>&lt;</code><code>redis-caches</code><code>&gt;</code>

<code>  </code><code>&lt;</code><code>redis-cache</code> <code>type</code><code>=</code><code>"org.tinygroup.redis.test.userdao"</code><code>&gt;</code>

<code>     </code><code>&lt;</code><code>redis-method</code> <code>method-name</code><code>=</code><code>"saveuser"</code><code>&gt;</code>

<code>           </code><code>&lt;</code><code>redis-expire</code> <code>value</code><code>=</code><code>"1000"</code><code>&gt;&lt;/</code><code>redis-expire</code><code>&gt;</code>

<code>           </code><code>&lt;</code><code>redis-string</code> <code>type</code><code>=</code><code>"user"</code> <code>key</code><code>=</code><code>"${user.id}"</code> <code>paramter-name</code><code>=</code><code>"user"</code><code>&gt;&lt;</code><code>redis-string</code><code>&gt;</code>

<code>     </code><code>&lt;/</code><code>redis-method</code><code>&gt;</code>

<code>  </code><code>&lt;/</code><code>redis-cache</code><code>&gt;</code>

<code>     </code><code>&lt;</code><code>redis-method</code> <code>method-name</code><code>=</code><code>"getuser"</code><code>&gt;</code>

<code>           </code><code>&lt;</code><code>redis-expire</code> <code>value</code><code>=</code><code>"1000"</code><code>&gt;&lt;/</code><code>redis-expire</code><code>&gt;</code>

<code>           </code><code>&lt;</code><code>redis-string</code> <code>type</code><code>=</code><code>"user"</code> <code>key</code><code>=</code><code>"${id}"</code> <code>&gt;&lt;</code><code>redis-string</code><code>&gt;</code>

<code>     </code><code>&lt;/</code><code>redis-method</code><code>&gt;</code>

<code>&lt;/</code><code>redis-caches</code><code>&gt;</code>

我們在實際應用當中,配置比上面的示例更完善,那現在我先講一下上面的兩段配置的含義。

在userdao的saveuser的時候,會同步的把user資料放到redis中進行緩沖,緩沖時間為1秒,存放的緩沖資料的類型為user,鍵值為${user.id},也就是要儲存的使用者的主健。實際進入到redis的時候,在redis中的健值是由上面類型與key的共同組成的。

在調用userdao的getuser的時候,會先從緩沖中擷取類型為user,鍵值為${id}的資料,如果緩沖中在,則取出并傳回,如果緩沖中沒有,則從原有業務代碼中取出值并放入緩沖,然後傳回此對象。

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

通過配置檔案外置,确實做到了對業務代碼的0侵入,但是如何為原有業務增加緩沖相當的業務邏輯呢?由于需求2要求可以new,也可以從容器中擷取對象執行個體,是以利用容器aop解決的跑就被封死了,是以,就得引入位元組碼的方式來進行解決。

寫一個基于maven的緩沖代碼處理插件,在編譯後增加此處理插件,根據配置檔案對原有代碼進行掃描并修改其位元組碼以添加緩沖相關處理邏輯。

現在隻要使用maven進行compile或install就可以自動添加緩沖相關的邏輯到class檔案中了。

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

具體實作,采用的技術就比較多了,有maven插件、有asm、有模闆引擎還有tiny架構的一些基礎工程,如:vfs,fileresolver等等。

如果采用tiny架構,可以直接拿來用,如果不用tiny架構,可以參照上面的思路做自己的實作。

繼續閱讀