cache源码分析三 evacuate机制的实现

xiaoxiao2021-02-28  89

原文: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;

转载请注明原文地址: https://www.6miu.com/read-56537.html

最新回复(0)