可以看到其实pool才是最终连接服务端的配置,看看pool0,它会连接10.2.225.210:13000,10.2.225.210:13001,10.2.225.210:13002这些机器和他们的端口,但是对于使用pool0的mclient0来说它仅仅只是知道有一个叫做mclient0的cache可以保存数据。此时slab就有三个:10.2.225.210:13000和10.2.225.210:13001和10.2.225.210:13002。

    当一个key:value要被放入到Memcached中,首先Memcached会根据key的hash算法获取到hash值来选择被分配的slab,然后根据value选择适合的dump区。所谓dump区其实就是根据value的大小来将内存按照存储单元内容大小分页。这个是可以配置Memcached的,例如Memcached将slab中的内存划分成4个dump,第一dump区存储0-50k大小的数据,第二dump区存储50-100k的数据,第三dump区存储100-500k的数据,第四dump区存储500-1000K的数据。那么当key:value需要被写入的时候,很容易定位到value所处的dump,分配内存给value。这种分dump模式简化内存管理,加速了内存回收和分配。但是这里需要注意的几点就是,首先当你的应用场景中保存的数据大小离散度很高,那么就不是很适合Memcached的这种分配模式,容易造成浪费,例如第一dump区已经满了,第二第三dump区都还是只有一个数据,那么第二第三dump区不会被回收,第二第三dump区的空间就浪费了。同时Memcached对于value的大小支持到1M,大于1M的内容不适合Memcached存储。其实在Cache的设计中这样的情况发生本来就证明设计有问题,Cache只是加速,一般保存都是较小的id或者小对象,用来验证以及为数据定位作精准细化,而大数据量的内容还是在数据库等存储中。

    知道了基本的分配机制以后再回过头来看看代码:

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

    //用来收集所有slab的dump号

    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来表示,那么证明是一个存储数据的dump,还有一些是age的部分

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

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

                  }

               }

            //根据收集到的dump来获取keys

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

               {

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

                  while(dumpIter.hasNext())

                  {

                      int dump = dumpIter.next();

    // statsCacheDump支持三个参数String[],int,int,第一个参数可以省略,默认填入null,表示从那些slab中获取dump号为第二个参数的keys,如果是null就从当前所有的slab中获取。第二个参数表示dump号,第三个参数表示返回最多多少个结果。

                      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,因为其实在我使用的这个java客户端存储的时候,默认会把key都作encoding一次,所以必须要做,不然会出现问题。

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

                            }

                            catch(Exception ex)

                            {

                                Logger.error(ex);

                            }

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

                            {

    //这里的fast参数是在方法参数中传入,作用是什么,其实采用这种搜索slab以及dump的方式获取keys会发现返回的可能还有一些已经移除的内容的keys,如果觉得需要准确的keys,就在做一次contains的检查,不过速度就会有一定的影响。

                                if (fast)

                                   keys.add(k);

                                else

                                   if (containsKey(k))

                                       keys.add(k);

                            }

                        }

                       }

                  }

               }

    至此,整个keySet的问题解决了,对于即时监控也基本都作好了,这里需要把过程中的两件小事情说一下。

    1.    statsCacheDump始终不能用。

    刚开始的时候statsCacheDump方法始终报错说连接超时,跟踪到了java客户端代码中发现并不是什么连接超时,只是服务端返回了错误信息,而客户端认为还没有结束一直等待,导致超时。我就顺手给java客户端的开发人员mail了信息求助(代码里面有email)。再仔细看了看出错信息,返回的是不认识该指令的错误,因此就去解压memcached的服务端,看了看它的协议说明,这个Stat方法还是有的,很奇怪,没有办法了,虽然自己对于c不是很懂,但起码大致看懂逻辑还是不难,下载了Memcached的源码一看,发现居然对于StatsCacheDump这个方法调用必须还有一个参数limit,在我手头的客户端代码里面就没有这个参数,所以错误了,本来想扩展一下那个方法,但是那个方法中实现的不是很好,都是private的不容易扩展,这时候居然收到其中一个客户端开发者的回复邮件,说我手头的代码太老了,同时不建议去实现keyset,认为这样比较低效。我去下载了一个新版本,看了看源码果然已经修复了,我就回了邮件表示感谢,同时也和他说明了这么做的原因。因此大家如果要和我一样写上面的代码,就需要它2.0.1的那个版本。这里对那些国外的开源工作者表示敬佩,对于开发者是很负责任的。

    2.关于fast那个选项

        这个是我加上去的,做了一下测试,例如我先执行如下代码:

        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);

    得到的结果为:

    Key1,key2,key3,key4

    Key3,key4

    可以看到其实如果通过StatsCacheDump来获取得到的keys会参杂一些已经失效的keys,只是没有回收,本来尝试获取时间戳来做判断,不过还不如使用containsKey来的有效。

    同时这里采用containsKey而不是用get,就是因为counter是不能用get获得的,即使counter存在。