Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1509554
  • 博文数量: 228
  • 博客积分: 1698
  • 博客等级: 上尉
  • 技术积分: 3241
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-24 21:49
个人简介

Linux

文章分类

全部博文(228)

文章存档

2017年(1)

2016年(43)

2015年(102)

2014年(44)

2013年(5)

2012年(30)

2011年(3)

分类: LINUX

2015-07-07 16:20:22

来源:http://blog.chinaunix.net/uid-23242010-id-2915354.html

之前分析过,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 
  3. 将1/16大小的cache对应的evacuate桶中状态错误的block删除
  4. evacuate_cleanup
  5. evacuate_cleanup->scan_for_pinned_documents->更新scan_pos信息,下次
  6. write_pos大于scan_pos,则再次执行periodic_scan
  7. periodic_scan
  8. 这个功能默认是不启用的。它的作用是扫描所有Dir信息,从中找出在此次扫描区域内的Dir,如果它的pin值满足要求,就执行force_evacuate_head。通过修改records.config文件中proxy.config.cache.permit.pinning值为1开启这个功能
  9. scan_for_pinned_documents
  10. 当cache写满时,执行该操作。具体操作dir_lookaside_cleanup->dir_clean_vol->periodic_scan,从lookaside buffer中,cache索引中,evacuate桶等中清除状态错误的信息
  11. 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;
阅读(3628) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~