转载至: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桶。
- evacuate桶是按照散列法实现的hash表,表中每一个桶是一个list,对应cache中EVACUATION_BUCKET_SIZE大小的存储区域。该函数负责剔除一个桶中状态错误的block
- evacuate_cleanup_blocks
- 将1/16大小的cache对应的evacuate桶中状态错误的block删除
- evacuate_cleanup
- evacuate_cleanup->scan_for_pinned_documents->更新scan_pos信息,下次
- write_pos大于scan_pos,则再次执行periodic_scan
- periodic_scan
- 这个功能默认是不启用的。它的作用是扫描所有Dir信息,从中找出在此次扫描区域内的Dir,如果它的pin值满足要求,就执行force_evacuate_head。通过修改records.config文件中proxy.config.cache.permit.pinning值为1开启这个功能
- scan_for_pinned_documents
- 当cache写满时,执行该操作。具体操作dir_lookaside_cleanup->dir_clean_vol->periodic_scan,从lookaside buffer中,cache索引中,evacuate桶等中清除状态错误的信息
- 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。
- traffic_line -s "proxy.config.cache.hit_evacuate_percent"-v 5
默认evacuate percent为0,就是不执行force_evacuate_head,通过上面的命令可以修改percent的值。
相关代码:
- //file: iocore/cache/CacheRead.cc CacheVC::openReadStartHead
- #ifdef HIT_EVACUATE
- if (vol->within_hit_evacuate_window(&dir) &&
- (!cache_config_hit_evacuate_size_limit || doc_len <= (uint64_t)cache_config_hit_evacuate_size_limit)) {
- DDebug("cache_hit_eva c", "dir: %d, write: %d, phase: %d",
- dir_off et(&dir), offset_to_vol_offset(vol, vol->header->write_pos), vol->header->phase);
- f.hit_evacuate = 1;
- }
- #endif
- //file: iocore/cache/CacheRead.cc CacheVC::openReadStartEarliest
- #ifdef HIT_EVACUATE
- if (vol->within_hit_evacuate_window(&earliest_dir) &&
- (!cache_config_hit_evacuate_size_limit || doc_len <= (uint64_t)cache_config_hit_evacuate_size_limit)) {
- DDebug("cache_hit_evac", "dir: %d, write: %d, phase: %d",
- dir_offset(&earliest_dir), offset_to_vol_offset(vol, vol->header->write_pos), vol->header->phase);
- f.hit_evacuate = 1;
- }
- #endif
- //file: iocore/cache/CacheRead.cc CacheVC::openReadClose
- #ifdef HIT_EVACUATE
- if (f.hit_evacuate && dir_valid(vol, &first_dir) && closed > 0) {
- if (f.single_fragment)
- vol->force_evacuate_head(&first_dir, dir_pinned(&first_dir));
- else if (dir_valid(vol, &earliest_dir)) {
- vol->force_evacuate_head(&first_dir, dir_pinned(&first_dir));
- vol->force_evacuate_head(&earliest_dir, dir_pinned(&earliest_dir));
- }
- }
- #endif
3. 发生evacuate
在cache每次写时,都会对要写的这块区域执行evacuate,具体代码在iocore/cache/CacheWrite.cc中的CacheVC::aggWrite中:
- // evacuate space
- off_t end = header->write_pos + agg_buf_pos + EVACUATION_SIZE;
- //对将要写入的cache区域执行evacuate
- if (evac_range(header->write_pos, end, !header->phase) < 0)
- goto Lwait;
- //如果要写的内容超出了cache大小,指针指向cache头部,此时需要对头部的那部分将被覆盖的区域执行evacuate
- if (end > skip + len)
- if (evac_range(start, start + (end - (skip + len)), header->phase))
- goto Lwait;