轩辕阁 发表于 2018-12-25 06:12:30

Memcached使用点滴 2

  可以看到其实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 = itemname
  // itemAtt = number
  // itemAtt = field
  String[] itemAtt = itemName.split(":");
  // 如果是itemName中是:number来表示,那么证明是一个存储数据的dump,还有一些是age的部分
  if (itemAtt.startsWith("number"))
  dumps.put(itemAtt, Integer.parseInt(itemAtt));
  }
  }
  //根据收集到的dump来获取keys
  if (!dumps.values().isEmpty())
  {
  Iterator 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存在。

页: [1]
查看完整版本: Memcached使用点滴 2