Chinaunix首页 | 论坛 | 博客
  • 博客访问: 151446
  • 博文数量: 50
  • 博客积分: 83
  • 博客等级: 民兵
  • 技术积分: 297
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-12 11:47
文章分类
文章存档

2012年(43)

2011年(7)

分类:

2012-05-27 11:05:45

          之前分析过,trafficserver的cache机制本质上是将cache视为一个ring buffer,循环顺序向cache写入内容。同时我们也说过,trafficserver对大文件与小文件的存储方式是不相同的。对于小文件,head与body是放在一个整体存储的,而对于大文件,head与body是分开存储的,同时body又会被分为几个fragment进行存储。
          问题1:当读取大文件时,刚刚读完head,而body部分由于cache写而被覆盖导致丢失,此次读取势必失败。
          解决方案:在cache写时,每次将正在读的大文件重新写一份。。
          问题2:如何将正在读的大文件重新写入cache?由于大文件是一个整体,同时悲催的是,它们又被分成几个fragment与一个head存储,在写时,需要考虑原子性。
          解决方案:为了解决原子性,可以将head与body作为两部分。如果head正在读,则对head执行evcuate操作。由于body可以由多个fragment组成,如果第一个fragment正在读,就判定body正在读,对所有fragment执行evacuate操作。
          问题3:如何实现evacuate机制?在对body的fragments执行evacuate操作时,如何确定下一次该写哪个fragment?
          解决方案:trafficserver使用一个hash list来实现evacuate机制,在代码中这个结构名为evacuate,为了区分,我称之为evacuate桶。对于正在读的文件,将其meta data信息加入到hash list中,同时使用一个引用计数来控制读取的次数,每读取一次,就+1,而读取完成后,则-1。如果计数器的值为0,则从hash list中删除。当cache写时,会查看要覆盖的区域对应的hash list内容,执行evacuate操作。为了实现大文件evacuate的原子性,trafficserver额外使用了一个hash list来保存body的第一个fragment,名为lookaside,这里我称之为lookaside buffer。当所有fragment都写成功后,才更新第一个fragment的Dir等信息。这样,当body没有evacuate完成时,head仍使用以前的body,由此可以避免evacuate body过程中读取失败。
          evacuate具体定义:将满足条件的正在读取的文件内容重新写入cache,同时更新索引信息,以避免cache写导致文件内容被覆盖而丢失。可以将evacuate机制看作是磁盘存储上的lru。
          evacuate相关函数
          1. 错误处理
          trafficserver周期扫描evacuate桶,将已经失效的信息从evacuate桶中删除。它的实现是,将cache分为16等分,每次写完1/16大小的cache,就会扫描一次evacuate桶。
  1. evacuate桶是按照散列法实现的hash表,表中每一个桶是一个list,对应cache中EVACUATION_BUCKET_SIZE大小的存储区域。该函数负责剔除一个桶中状态错误的block
  2. evacuate_cleanup_blocks  
  1. 将1/16大小的cache对应的evacuate桶中状态错误的block删除
  2. evacuate_cleanup
  1. evacuate_cleanup->scan_for_pinned_documents->更新scan_pos信息,下次
  2. write_pos大于scan_pos,则再次执行periodic_scan
  3. periodic_scan
  1. 这个功能默认是不启用的。它的作用是扫描所有Dir信息,从中找出在此次扫描区域内的Dir,如果它的pin值满足要求,就执行force_evacuate_head。通过修改records.config文件中proxy.config.cache.permit.pinning值为1开启这个功能
  2. scan_for_pinned_documents
  1. 当cache写满时,执行该操作。具体操作dir_lookaside_cleanup->dir_clean_vol->periodic_scan,从lookaside buffer中,cache索引中,evacuate桶等中清除状态错误的信息
  2. agg_wrap
          2 向evacuate桶中添加希望执行evacuate的文件
          (1) 当正在读取大文件时,如果要向读取的cache区域写入新数据,此时会发生evacuate。Vol::begin_read将要读取的大文件信息加入evacuate桶中,如果已经在evacuate桶中,则增加引用计数,当这次大文件读取结束,执行Vol::close_read减少引用计数,如果为0,从evacuate桶中移除。这个过程是防御性质的,如果在cache开始执行evacute时,大文件信息已经从evacuate桶中移除,则不会执行evacuate。
          (2) 还有一种方式是force_evacuate_head。如果正在读取的文件在cache的位置相对cache当前写位置在(cache_size * evacute percent/100)范围之内,就强制执行evacuate。这种方式一定会发生evacuate。
  1. traffic_line -s "proxy.config.cache.hit_evacuate_percent"-v 5
          默认evacuate percent为0,就是不执行force_evacuate_head,通过上面的命令可以修改percent的值。
          相关代码:
  1. //file: iocore/cache/CacheRead.cc  CacheVC::openReadStartHead
  2. #ifdef HIT_EVACUATE
  3. if (vol->within_hit_evacuate_window(&dir) &&
  4. (!cache_config_hit_evacuate_size_limit || doc_len <= (uint64_t)cache_config_hit_evacuate_size_limit)) {
  5.     DDebug("cache_hit_eva c", "dir: %d, write: %d, phase: %d",
  6.     dir_off et(&dir), offset_to_vol_offset(vol, vol->header->write_pos), vol->header->phase);
  7.     f.hit_evacuate = 1;
  8. }
  9. #endif 
  1. //file: iocore/cache/CacheRead.cc CacheVC::openReadStartEarliest
  2. #ifdef HIT_EVACUATE
  3. if (vol->within_hit_evacuate_window(&earliest_dir) &&
  4. (!cache_config_hit_evacuate_size_limit || doc_len <= (uint64_t)cache_config_hit_evacuate_size_limit)) {
  5.     DDebug("cache_hit_evac", "dir: %d, write: %d, phase: %d",
  6.     dir_offset(&earliest_dir), offset_to_vol_offset(vol, vol->header->write_pos), vol->header->phase);
  7.     f.hit_evacuate = 1;
  8. }
  9. #endif
  1. //file: iocore/cache/CacheRead.cc CacheVC::openReadClose
  2. #ifdef HIT_EVACUATE
  3. if (f.hit_evacuate && dir_valid(vol, &first_dir) && closed > 0) {
  4.     if (f.single_fragment)
  5.         vol->force_evacuate_head(&first_dir, dir_pinned(&first_dir));
  6.     else if (dir_valid(vol, &earliest_dir)) {
  7.         vol->force_evacuate_head(&first_dir, dir_pinned(&first_dir));
  8.         vol->force_evacuate_head(&earliest_dir, dir_pinned(&earliest_dir));
  9.     }
  10. }
  11. #endif
          3. 发生evacuate
          在cache每次写时,都会对要写的这块区域执行evacuate,具体代码在iocore/cache/CacheWrite.cc中的CacheVC::aggWrite中:
  1. // evacuate space
  2. off_t end = header->write_pos + agg_buf_pos + EVACUATION_SIZE;
  3. //对将要写入的cache区域执行evacuate
  4. if (evac_range(header->write_pos, end, !header->phase) < 0)
  5.     goto Lwait;
  6. //如果要写的内容超出了cache大小,指针指向cache头部,此时需要对头部的那部分将被覆盖的区域执行evacuate
  7. if (end > skip + len)
  8.     if (evac_range(start, start + (end - (skip + len)), header->phase))
  9.         goto Lwait;











          
阅读(829) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~