<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    放翁(文初)的一畝三分地

      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      210 隨筆 :: 1 文章 :: 320 評論 :: 0 Trackbacks

     

           我對于Memcached的接觸,還是在去年看了CSDN的一系列國外大型網(wǎng)站架構(gòu)設(shè)計(jì)而開始的。最初的時(shí)候只是簡單的封裝了Memcached Java版的客戶端,主要是對于配置的簡化以及Memcached多點(diǎn)備份作了一些工作,然后就作為ASF的組件一部分提供給其他Team使用。其實(shí)看過Memcached Java客戶端代碼的人就會了解其實(shí)客戶端的事情很簡單,就是要有一套高性能的Socket通信框架以及對Memcached的私有協(xié)議實(shí)現(xiàn)的接口,自己去做這些事情也是很簡單的,不過既然有可以滿足自己需求的開源部分,那么就去實(shí)現(xiàn)自己需要的但沒有實(shí)現(xiàn)的。這里我用的是Whalin的客戶端版本,這里為什么還要提出來講這個(gè),后面會提到。

           在對Java客戶端作了簡單封裝和擴(kuò)展以后,由于其他Team使用的沒有什么特殊需求,也就沒有再去做太多的修改,直到最近自己的服務(wù)集成平臺需要做服務(wù)訪問控制,才重新豐富了Cache組件,也就是這個(gè)過程中對于Memcached的一些特性和小的細(xì)節(jié)有了一些新的認(rèn)識。

           作為服務(wù)集成平臺需要對服務(wù)有所監(jiān)控,包括訪問頻率控制以及訪問次數(shù)控制。頻率控制其實(shí)很類似于硬件方面的頻率控制,例如硬件可以對IP的高頻率訪問視為攻擊,列入黑名單。而作為服務(wù)的訪問,對于服務(wù)訪問者的控制其實(shí)涉及到了業(yè)務(wù)參數(shù),那么硬件就不是很適合去做這方面的控制,為此我也考慮了很久,最開始打算在Apache上做一個(gè)模塊控制,但是最后覺得還是放在后面的業(yè)務(wù)框架上做這件事情。當(dāng)然后面我說說的方案可能并不好,但是也算是一種想法。要把頻繁的訪問數(shù)據(jù)記錄下來同時(shí)分析,那么數(shù)據(jù)庫肯定是不行的,最簡單的方式就是采用Cache,又因?yàn)槭羌悍秶鷥?nèi)的控制,那么集中式Cache就非Memcached莫數(shù)了(分布式的Cache傳播本身損耗太大,集中式Cache本來的最大缺點(diǎn)就是單點(diǎn),但作簡單的備份操作就可以基本解決此類問題)。

           作為解決這個(gè)問題的方法來說只需要實(shí)現(xiàn)兩部分工作:訪問計(jì)數(shù)器,定時(shí)任務(wù)。定時(shí)任務(wù)在我做日志分析框架的時(shí)候都是采用了Jdk5Concurrent包里面的ScheduledExecutorService,這個(gè)作簡單的循環(huán)任務(wù)足夠用了,同時(shí)也是有很好的多線程異步支持,復(fù)雜一點(diǎn)么用Quartz。計(jì)數(shù)器就要靠Memcached來實(shí)現(xiàn)了,本來一般的Cache最大的問題就是高并發(fā)下的事務(wù)保證,如果采用Get+Set來完成計(jì)數(shù)的話,那么高并發(fā)下計(jì)數(shù)器就會出現(xiàn)讀寫不一致性的問題,幸好Memcached提供了計(jì)數(shù)累加功能,讓這種累加動作能夠在服務(wù)端一次做好,服務(wù)端控制并發(fā)寫入,保證數(shù)據(jù)的一致性。

    下面就看看以下幾個(gè)方法:

    boolean storeCounter(String key, long count):存儲key的計(jì)數(shù)器,值為count

    long getCounter(String key):獲取key的計(jì)數(shù)器,如果不存在返回-1。

    long addOrDecr(String key, long decr):計(jì)數(shù)器值減去decr,如果計(jì)數(shù)器不存在,保存decr作為計(jì)數(shù)器值

    long addOrIncr(String key, long inc):計(jì)數(shù)器值增加inc,如果計(jì)數(shù)器不存在,保存inc作為計(jì)數(shù)器值

    long decr(String key, long decr):與addOrDecr不同的是在計(jì)數(shù)器不存在的時(shí)候不保存任何值,返回-1

    long incr(String key, long inc) :與addOrIncr不同的是在計(jì)數(shù)器不存在的時(shí)候不保存任何值,返回-1

    這里需要說明幾點(diǎn):

    storeCounter和普通的set方法不同,如果通過set方式置入key:value的話,getCounter等其他四個(gè)方法都認(rèn)為技術(shù)器不存在。所以Counter的存儲方式是和普通內(nèi)容存儲不同的。

    在不同的場景要慎用addOrXXXXXXXX的方法,兩者還是有比較大的區(qū)別的。

    計(jì)數(shù)器沒有提供移除特殊方法,使用delete方法可以移除計(jì)數(shù)器,但是頻繁的deleteaddOrXXXX有時(shí)候會出現(xiàn)一些奇怪的問題(例如同名的計(jì)數(shù)器就沒有辦法再次被創(chuàng)建,不過這個(gè)還需要進(jìn)一步的去研究一下看看)。一般情況下如果計(jì)數(shù)器的key不是很多,同時(shí)也會被復(fù)用,那么可以通過置為0或者減去已經(jīng)分析過的數(shù)量來復(fù)位。

           有上面的一套計(jì)數(shù)器機(jī)制就可以很方便的實(shí)現(xiàn)Memcached的計(jì)數(shù)功能,但是又一個(gè)問題出現(xiàn)了,如何讓定時(shí)任務(wù)去遍歷計(jì)數(shù)器,分析計(jì)數(shù)器是否到了閥值,觸發(fā)創(chuàng)建黑名單記錄的工作。早先我同事希望我能夠提供封裝好的keySet接口,但是我自己覺得其實(shí)作為Cache來說簡單就是最重要的,Cache不需要去遍歷。首先使用Cache的角色就應(yīng)該知道Key,然后去Cache里面找,找不到就去后臺例如DB里面去搜索,然后將搜索的結(jié)果在考慮更新到Cache里面,這樣才是最高效并且最可靠的,Cache靠不住阿,隨時(shí)都可能會丟失或者崩潰,因此作為類似于一級緩存或者這類數(shù)據(jù)完整性要求不高,性能要求很高的場景使用最合適。當(dāng)時(shí)就沒有提供這樣的接口,直到今天自己需要了,才考慮如何去做這件事情。

           開始考慮是否能夠?qū)?/span>key都記錄在另外的Cache中或者是Memcached中,首先在高并發(fā)下更新操作就是一大問題,再者Memcached的內(nèi)存分配回收機(jī)制以及Value的大小限制都不能滿足這樣的需求,如果使用數(shù)據(jù)庫,那么頻繁更新操作勢必不可行,采用異步緩存刷新又有一個(gè)時(shí)間間隔期,同時(shí)更新也不是很方便。最后考慮如果能夠讓Memcached實(shí)現(xiàn)Keyset那么就是最好的解決方案,網(wǎng)上搜索了一下,找到一種策略,然后自己優(yōu)化了一下,優(yōu)化后的代碼如下:

        @SuppressWarnings("unchecked")

        public Set keySet(int limit,boolean fast)

        {

           Set<String> keys = new HashSet<String>();

           Map<String,Integer> dumps = new HashMap<String,Integer>();

                

           Map slabs = getCacheClient().statsItems();

          

           if (slabs != null && slabs.keySet() != null)

           {

               Iterator itemsItr = slabs.keySet().iterator();

              

               while(itemsItr.hasNext())

               {

                  String server = itemsItr.next().toString();

                  Map itemNames = (Map) slabs.get(server);

                  Iterator itemNameItr = itemNames.keySet().iterator();

                 

                  while(itemNameItr.hasNext())

                  {

                      String itemName = itemNameItr.next().toString();

                     

                      // itemAtt[0] = itemname

                       // itemAtt[1] = number

                       // itemAtt[2] = field

                       String[] itemAtt = itemName.split(":");

                      

                       if (itemAtt[2].startsWith("number"))

                           dumps.put(itemAtt[1], Integer.parseInt(itemAtt[1]));

                  }

               }

              

               if (!dumps.values().isEmpty())

               {

                  Iterator<Integer> dumpIter = dumps.values().iterator();

                 

                  while(dumpIter.hasNext())

                  {

                      int dump = dumpIter.next();

                     

                      Map cacheDump = statsCacheDump(dump,limit);

                     

                      Iterator entryIter = cacheDump.values().iterator();

                     

                      while (entryIter.hasNext())

                       {

                           Map items = (Map)entryIter.next();

                          

                           Iterator ks = items.keySet().iterator();

                          

                           while(ks.hasNext())

                           {

                              String k = (String)ks.next();

                             

                              try

                              {

                                  k = URLDecoder.decode(k,"UTF-8");

                              }

                              catch(Exception ex)

                              {

                                  Logger.error(ex);

                              }

                              if (k != null && !k.trim().equals(""))

                              {

                                  if (fast)

                                     keys.add(k);

                                  else

                                     if (containsKey(k))

                                         keys.add(k);

                              }

                           }

                       }

                     

                  }

               }

           }

          

           return keys;

        }  

    對于上面代碼的了解需要從Memcached內(nèi)存分配和回收機(jī)制開始,以前接觸Memcached的時(shí)候只是了解,這部分代碼寫了以后就有些知道怎么回事了。Memcached為了提高內(nèi)存的分配和回收效率,采用了slabdump分區(qū)的概念。Memcached一大優(yōu)勢就是能夠充分利用Memory資源,將同機(jī)器或者不同機(jī)器的Memcached服務(wù)端組合成為對客戶端看似統(tǒng)一的存儲空間,Memcached可以在一臺機(jī)器上開多個(gè)端口作為服務(wù)端多個(gè)實(shí)例,也可以在多臺機(jī)器上開多個(gè)服務(wù)實(shí)例,而slab就是Memcached的服務(wù)端。下面是我封裝后的Cache配置:

    <?xml version="1.0" encoding="UTF-8"?>

    <memcached>

       

        <client name="mclient0" compressEnable="true" defaultEncoding="UTF-8" socketpool="pool0">

            <!--errorHandler></errorHandler-->

        </client>

       

        <client name="mclient1" compressEnable="true" defaultEncoding="UTF-8" socketpool="pool1">

            <!--errorHandler></errorHandler-->

        </client>

       

        <client name="mclient11" compressEnable="true" defaultEncoding="UTF-8" socketpool="pool11">

            <!--errorHandler></errorHandler-->

        </client>

       

        <socketpool name="pool0" failover="true" initConn="10" minConn="5" maxConn="250" maintSleep="0"

            nagle="false" socketTO="3000" aliveCheck="true">

            <servers>10.2.225.210:13000,10.2.225.210:13001,10.2.225.210:13002</servers>

        </socketpool> 

       

        <socketpool name="pool1" failover="true" initConn="10" minConn="5" maxConn="250" maintSleep="0"

            nagle="false" socketTO="3000" aliveCheck="true">

            <servers>10.2.225.210:13000</servers>

        </socketpool>  

        <socketpool name="pool11" failover="true" initConn="10" minConn="5" maxConn="250" maintSleep="0"

            nagle="false" socketTO="3000" aliveCheck="true">

            <servers>10.2.225.210:13000</servers>

        </socketpool>  

        <cluster name="cluster1">

            <memCachedClients>mclient1,mclient11</memCachedClients>

        </cluster>

    </memcached>

    可以看到其實(shí)pool才是最終連接服務(wù)端的配置,看看pool0,它會連接10.2.225.210:13000,10.2.225.210:13001,10.2.225.210:13002這些機(jī)器和他們的端口,但是對于使用pool0mclient0來說它僅僅只是知道有一個(gè)叫做mclient0cache可以保存數(shù)據(jù)。此時(shí)slab就有三個(gè):10.2.225.210:1300010.2.225.210:1300110.2.225.210:13002。

    當(dāng)一個(gè)key:value要被放入到Memcached中,首先Memcached會根據(jù)keyhash算法獲取到hash值來選擇被分配的slab,然后根據(jù)value選擇適合的dump區(qū)。所謂dump區(qū)其實(shí)就是根據(jù)value的大小來將內(nèi)存按照存儲單元內(nèi)容大小分頁。這個(gè)是可以配置Memcached的,例如Memcachedslab中的內(nèi)存劃分成4個(gè)dump,第一dump區(qū)存儲0-50k大小的數(shù)據(jù),第二dump區(qū)存儲50-100k的數(shù)據(jù),第三dump區(qū)存儲100-500k的數(shù)據(jù),第四dump區(qū)存儲500-1000K的數(shù)據(jù)。那么當(dāng)key:value需要被寫入的時(shí)候,很容易定位到value所處的dump,分配內(nèi)存給value。這種分dump模式簡化內(nèi)存管理,加速了內(nèi)存回收和分配。但是這里需要注意的幾點(diǎn)就是,首先當(dāng)你的應(yīng)用場景中保存的數(shù)據(jù)大小離散度很高,那么就不是很適合Memcached的這種分配模式,容易造成浪費(fèi),例如第一dump區(qū)已經(jīng)滿了,第二第三dump區(qū)都還是只有一個(gè)數(shù)據(jù),那么第二第三dump區(qū)不會被回收,第二第三dump區(qū)的空間就浪費(fèi)了。同時(shí)Memcached對于value的大小支持到1M,大于1M的內(nèi)容不適合Memcached存儲。其實(shí)在Cache的設(shè)計(jì)中這樣的情況發(fā)生本來就證明設(shè)計(jì)有問題,Cache只是加速,一般保存都是較小的id或者小對象,用來驗(yàn)證以及為數(shù)據(jù)定位作精準(zhǔn)細(xì)化,而大數(shù)據(jù)量的內(nèi)容還是在數(shù)據(jù)庫等存儲中。

    知道了基本的分配機(jī)制以后再回過頭來看看代碼:

    Map slabs = getCacheClient().statsItems();//獲取所有的slab

    //用來收集所有slabdump

    while(itemsItr.hasNext())

               {

                  String server = itemsItr.next().toString();

                  Map itemNames = (Map) slabs.get(server);

                  Iterator itemNameItr = itemNames.keySet().iterator();

                 

                  while(itemNameItr.hasNext())

                  {

                      String itemName = itemNameItr.next().toString();

                     

                      // itemAtt[0] = itemname

                       // itemAtt[1] = number

                       // itemAtt[2] = field

                       String[] itemAtt = itemName.split(":");

                      

    // 如果是itemName中是:number來表示,那么證明是一個(gè)存儲數(shù)據(jù)的dump,還有一些是age的部分

                       if (itemAtt[2].startsWith("number"))

                       dumps.put(itemAtt[1], Integer.parseInt(itemAtt[1]));

                  }

               }

          

            //根據(jù)收集到的dump來獲取keys

    if (!dumps.values().isEmpty())

               {

                  Iterator<Integer> dumpIter = dumps.values().iterator();

                 

                  while(dumpIter.hasNext())

                  {

                      int dump = dumpIter.next();

                     

    // statsCacheDump支持三個(gè)參數(shù)String[],int,int,第一個(gè)參數(shù)可以省略,默認(rèn)填入null,表示從那些slab中獲取dump號為第二個(gè)參數(shù)的keys,如果是null就從當(dāng)前所有的slab中獲取。第二個(gè)參數(shù)表示dump號,第三個(gè)參數(shù)表示返回最多多少個(gè)結(jié)果。

                      Map cacheDump = statsCacheDump(dump,limit);

                     

                      Iterator entryIter = cacheDump.values().iterator();

                     

                      while (entryIter.hasNext())

                       {

                            Map items = (Map)entryIter.next();

                        

                            Iterator ks = items.keySet().iterator();

                        

                        while(ks.hasNext())

                        {

                            String k = (String)ks.next();

                           

                            try

                            {

    //這里為什么要作decode,因?yàn)槠鋵?shí)在我使用的這個(gè)java客戶端存儲的時(shí)候,默認(rèn)會把key都作encoding一次,所以必須要做,不然會出現(xiàn)問題。

                                k = URLDecoder.decode(k,"UTF-8");

                            }

                            catch(Exception ex)

                            {

                                Logger.error(ex);

                            }

                            if (k != null && !k.trim().equals(""))

                            {

    //這里的fast參數(shù)是在方法參數(shù)中傳入,作用是什么,其實(shí)采用這種搜索slab以及dump的方式獲取keys會發(fā)現(xiàn)返回的可能還有一些已經(jīng)移除的內(nèi)容的keys,如果覺得需要準(zhǔn)確的keys,就在做一次contains的檢查,不過速度就會有一定的影響。

                                if (fast)

                                   keys.add(k);

                                else

                                   if (containsKey(k))

                                       keys.add(k);

                            }

                        }

                       }

                     

                  }

               }

    至此,整個(gè)keySet的問題解決了,對于即時(shí)監(jiān)控也基本都作好了,這里需要把過程中的兩件小事情說一下。

    1.    statsCacheDump始終不能用。

    剛開始的時(shí)候statsCacheDump方法始終報(bào)錯(cuò)說連接超時(shí),跟蹤到了java客戶端代碼中發(fā)現(xiàn)并不是什么連接超時(shí),只是服務(wù)端返回了錯(cuò)誤信息,而客戶端認(rèn)為還沒有結(jié)束一直等待,導(dǎo)致超時(shí)。我就順手給java客戶端的開發(fā)人員mail了信息求助(代碼里面有email)。再仔細(xì)看了看出錯(cuò)信息,返回的是不認(rèn)識該指令的錯(cuò)誤,因此就去解壓memcached的服務(wù)端,看了看它的協(xié)議說明,這個(gè)Stat方法還是有的,很奇怪,沒有辦法了,雖然自己對于c不是很懂,但起碼大致看懂邏輯還是不難,下載了Memcached的源碼一看,發(fā)現(xiàn)居然對于StatsCacheDump這個(gè)方法調(diào)用必須還有一個(gè)參數(shù)limit,在我手頭的客戶端代碼里面就沒有這個(gè)參數(shù),所以錯(cuò)誤了,本來想擴(kuò)展一下那個(gè)方法,但是那個(gè)方法中實(shí)現(xiàn)的不是很好,都是private的不容易擴(kuò)展,這時(shí)候居然收到其中一個(gè)客戶端開發(fā)者的回復(fù)郵件,說我手頭的代碼太老了,同時(shí)不建議去實(shí)現(xiàn)keyset,認(rèn)為這樣比較低效。我去下載了一個(gè)新版本,看了看源碼果然已經(jīng)修復(fù)了,我就回了郵件表示感謝,同時(shí)也和他說明了這么做的原因。因此大家如果要和我一樣寫上面的代碼,就需要它2.0.1的那個(gè)版本。這里對那些國外的開源工作者表示敬佩,對于開發(fā)者是很負(fù)責(zé)任的。

    2.關(guān)于fast那個(gè)選項(xiàng)

        這個(gè)是我加上去的,做了一下測試,例如我先執(zhí)行如下代碼:

        Cache.set(“key1”,”value1”);

    Cache.set(“key2”,”value2”);

    Cache.flushAll(null);

    Cache.set(“key3”,”value3”);

    Cache.set(“key4”,”value4”);

    Boolean fast = true;

    Set keys = Cache.keySet(fast);

    System.out.println(keys);

    Fast = false;

    keys = Cache.keySet(fast);

    System.out.println(keys);

    得到的結(jié)果為:

    Key1,key2,key3,key4

    Key3,key4

    可以看到其實(shí)如果通過StatsCacheDump來獲取得到的keys會參雜一些已經(jīng)失效的keys,只是沒有回收,本來嘗試獲取時(shí)間戳來做判斷,不過還不如使用containsKey來的有效。

    同時(shí)這里采用containsKey而不是用get,就是因?yàn)?/span>counter是不能用get獲得的,即使counter存在。

    這些就是今天在使用Memcached所收獲的,分享一下,如果有一些理解上的偏差也希望能夠被指出。

    posted on 2008-06-04 23:10 岑文初 閱讀(8975) 評論(2)  編輯  收藏

    評論

    # re: Memcached使用點(diǎn)滴 2008-07-01 18:43 茴香豆
    寫的很好,詳細(xì)說明了實(shí)際開發(fā)中遇到的細(xì)節(jié)問題,贊一個(gè)!  回復(fù)  更多評論
      

    # re: Memcached使用點(diǎn)滴 2008-11-20 09:55 mile
    榜樣!  回復(fù)  更多評論
      


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


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 好久久免费视频高清| 美女被吸屁股免费网站| 久9这里精品免费视频| 亚洲人成色77777在线观看大| 青草久久精品亚洲综合专区| 日本一道一区二区免费看| 亚洲欧美国产国产综合一区| 午夜时刻免费入口| 亚洲女女女同性video| 国产99视频免费精品是看6| 成人婷婷网色偷偷亚洲男人的天堂| 日韩一级免费视频| 黄色三级三级三级免费看| 亚洲线精品一区二区三区| 七色永久性tv网站免费看| 亚洲高清不卡视频| 成年黄网站色大免费全看| 亚洲成_人网站图片| 免费观看四虎精品国产永久| ssswww日本免费网站片| 亚洲a在线视频视频| 国产精品免费精品自在线观看| 亚洲五月综合缴情婷婷| 四虎永久精品免费观看| 久久WWW免费人成—看片| 亚洲AV日韩精品久久久久久| 免费国产成人高清在线观看网站| 亚洲精品成a人在线观看夫 | 毛片免费在线观看网址| 国产亚洲精彩视频| 国产亚洲成AV人片在线观黄桃 | 一本色道久久综合亚洲精品高清| 91精品成人免费国产| 亚洲国产精品综合久久2007| 四虎影院永久免费观看| 在线看片免费人成视频福利| 亚洲人成人77777在线播放| 亚洲福利精品一区二区三区| 99精品视频在线视频免费观看| 亚洲熟女综合一区二区三区| 永久亚洲成a人片777777|