10 使用合并文件存储trunk_file

来源:互联网 发布:qtassist是什么软件 编辑:程序博客网 时间:2024/06/03 15:38

一、配置参数

合并文件存储相关的配置都在tracker.conf中。配置完成后,重启tracker和storage server。

1、use_trunk_file :是否使用合并存储,缺省关闭。

2、slot_min_size:为一个小文件分配的最小空间,缺省256字节。即使你上传的文件只有0字节,也会分配这么多空间。

3、slot_max_size:使用合并存储的最大文件大小,缺省为16MB,超过这个size的文件,不会存储到trunk file中,而是作为一个单独的文件直接存储到文件系统中。

4、 trunk_file_size:trunk file大小,缺省为64MB。不要配置得过大或者过小,最好不要超过256MB。

5、trunk_create_file_advance:是否提前创建trunk file,缺省关闭。

6、trunk_create_file_time_base:提前创建trunk file的时间点,比如可以指定半夜02:00创建

7、trunk_create_file_interval:提前创建trunk file的时间间隔,单位为秒。如果一天创建一次,可以设置为86400。

8、trunk_create_file_space_threshold:创建trunk file需要达到的可用空间大小。如果空闲的trunk file空间大于本参数,则不会提前创建。

9、trunk_init_check_occupying:trunk server初始化加载空闲blocks时,是否检查对应trunk file,看这个block是否已被占用。缺省为false。启用了本参数,trunk server初始化时间将大大延长。除非必要,请不要打开本选项。

10、trunk_init_reload_from_binlog:忽略快照文件,只从trunk binlog中加载空闲block。缺省为false。只要当从v3.10以下版本升级到v3.10以上版本时,可能才需要打开本选项。


二、原理

1、storage server内置trunk manager的功能,在storage server上对trunk file可用区块进行管理。在一个时间点,一个group只有一台storage server提供管理和查询服务,简称trunk manager。该group的其余storage server作为备机,只接收binlog。
如何做到一个group只有一台storage server提供trunk管理服务,这个由tracker server统一协调完成。如果承担trunk manager那台storage server挂了,本组其余的一台storage server会自动升级为trunk manager,接替其工作。

2、trunk manager将trunk相关数据,全部存放到内存中管理。当storage server要存储一个小于阀值的文件(也就是小文件)时,先询问trunk manager,trunk manager返回存储到的trunk file文件名,以及存储起始的偏移量。当storage server成功完成文件存储后,向trunk manager报告。如果报告失败,则文件上传当失败处理。

3、trunk manager管理方案说明:
trunk manager将trunk相关数据,全部存放到内存中管理。对于trunk更新操作(包括增加和删除两种),会记录到单独的binlog文件中,有专门的线程(trunk_sync_thread_start创建该线程)将binlog文件同步给本组的其他storage server。为了节约内存空间,trunk file文件名,会单独存放。trunk file可用空间链表中,trunk filename采用指针方式指向。

当storage server向trunk manager请求分配文件空间时,trunk manager会先在内存中扫描有没有满足条件的可用trunk,如果有,那么直接返回。否则,一个创建trunk file,然后将新的trunk file记录到binlog和内存中,并完成分配。

为了提高分配效率,trunk manager将采取slot的方式对可用空间进行组织,比如初始的字节数为256,最大字节数为32MB,每次以2倍的速度递增,形如:
256,512, 1K, 2K, 4K,。。。,1M,2M,4M,。。。,16M,32M
在slot 256中的可用空间,是 >= 256,< 512的
在slot 512中的可用空间,是 >= 512,< 1K的
在slot 1K中的可用空间,是 >= 1K,< 2K 的
以此类推。
初次分配时,新创建的trunk file是在slot 32M中。随着trunk file被逐渐使用,可能会从slot 32M移动到slot 16M中,后面又可能被移动到slot 1MB中,如此等等
每个slot下可用的空间信息,按可使用空间大小升序排列。这么做的好处是分配的效率会比较高,直接取第一个结点(链表头)即可。

为了简洁起见,不采用相邻空闲空间合并机制。

4、为了简化,trunk server的确存在单点问题。为了减少单点风险,trunk server会把更新binlog同步到同组的其他storage server上
万一trunk server挂了,由tracker server来协调,选举出新的trunk server。新选举出的trunk server,从binlog中加载已有数据,然后承担trunk server的功能。
判断trunk server挂掉,有一定的超时机制。比如5分钟内,trunk server都处于离线状态,则认为trunk server挂掉。
storage server升级为trunk server,由storage server主动申请的方式
多台tracker server,按ip地址升序排列。storage server向第一台tracker server申请成为trunk server,申请成功后,通知其他tracker server。


三、trunk manager的选出

relationship_thread_entrance->relationship_select_leader->被选为leader的tracker server将调用tracker_mem_find_trunk_servers->对每个组,如果pTrunkServer == NUL,则进一步调用tracker_mem_find_trunk_server(*ppGroup, true)。

static int tracker_mem_find_trunk_server(FDFSGroupInfo *pGroup, const bool save){FDFSStorageDetail *pStoreServer;FDFSStorageDetail **ppServer;FDFSStorageDetail **ppServerEnd;int result;int64_t file_size;int64_t max_file_size;pStoreServer = pGroup->pStoreServer;if (pStoreServer == NULL){return ENOENT;}result = tracker_mem_get_trunk_binlog_size(pStoreServer->ip_addr, pGroup->storage_port, &max_file_size);if (result != 0){return result;}ppServerEnd = pGroup->active_servers + pGroup->active_count;for (ppServer=pGroup->active_servers; ppServer<ppServerEnd; ppServer++){if (*ppServer == pStoreServer){continue;}result = tracker_mem_get_trunk_binlog_size((*ppServer)->ip_addr, pGroup->storage_port, &file_size);if (result != 0){continue;}if (file_size > max_file_size){pStoreServer = *ppServer; //找到trunk binlog长度最大的那个storage server。}}return tracker_mem_do_set_trunk_server(pGroup, \pStoreServer, save); //设置trunk server并log。}

四、从文件上传看trunk

1、storage_deal_task->storage_upload_file。storage_upload_file部分代码如下:

if (pFileContext->extra_info.upload.file_type & _FILE_TYPE_TRUNK){FDFSTrunkFullInfo *pTrunkInfo;pFileContext->extra_info.upload.if_sub_path_alloced = true;pTrunkInfo = &(pFileContext->extra_info.upload.trunk_info); //此时pTrunkInfo只是做了清零初始化,还没有数据。if ((result=trunk_client_trunk_alloc_space( \TRUNK_CALC_SIZE(file_bytes), pTrunkInfo)) != 0)  //首先分配trunk的空间。从此跟踪可以查看trunk的内存管理。{pClientInfo->total_length = sizeof(TrackerHeader);return result;}clean_func = dio_trunk_write_finish_clean_up;file_offset = TRUNK_FILE_START_OFFSET((*pTrunkInfo));        pFileContext->extra_info.upload.if_gen_filename = true;trunk_get_full_filename(pTrunkInfo, pFileContext->filename, \sizeof(pFileContext->filename));pFileContext->extra_info.upload.before_open_callback = \dio_check_trunk_file_when_upload;pFileContext->extra_info.upload.before_close_callback = \dio_write_chunk_header;pFileContext->open_flags = O_RDWR | g_extra_open_file_flags;}else{......} return storage_write_to_file(pTask, file_offset, file_bytes, \p - pTask->data, dio_write_file, \storage_upload_file_done_callback, \clean_func, store_path_index);

2、trunk_client_trunk_alloc_space
int trunk_client_trunk_alloc_space(const int file_size, \FDFSTrunkFullInfo *pTrunkInfo){int result;ConnectionInfo trunk_server;ConnectionInfo *pTrunkServer;if (g_if_trunker_self) //如果自己就是trunk server。{return trunk_alloc_space(file_size, pTrunkInfo); //本例的trunk server就是自身,执行此处。新建trunk文件,调用trunk_split(),这应该就是讲真个trunk文件划分成多个slot管理,便于分配。}if (*(g_trunk_server.ip_addr) == '\0'){logError("file: "__FILE__", line: %d, " \"no trunk server", __LINE__);return EAGAIN;}memcpy(&trunk_server, &g_trunk_server, sizeof(ConnectionInfo));if ((pTrunkServer=tracker_connect_server(&trunk_server, &result)) == NULL){return result;}result = trunk_client_trunk_do_alloc_space(pTrunkServer, \file_size, pTrunkInfo);//发送STORAGE_PROTO_CMD_TRUNK_ALLOC_SPACE命令给trunk server。tracker_disconnect_server_ex(pTrunkServer, result != 0);return result;}

3、trunk内存管理的数据结构:

(1)static struct fast_mblock_man free_blocks_man; //管理free节点。
static struct fast_mblock_man tree_nodes_man;

struct fast_mblock_man  {struct fast_mblock_node *free_chain_head;     //free node chainstruct fast_mblock_malloc *malloc_chain_head; //malloc chain to be freed(fast_mblock_malloc管理一段连续的fast_mblock_node,每个32字节,本例一次性分配了32768个节点,也就是我们可以创建32768个trunk文件,用完了这么多节点后,才需要重新分配。)int element_size;         //element sizeint alloc_elements_once;  //alloc elements oncepthread_mutex_t lock;     //the lock for read / write free node chain};

(2)FDFSTrunkNode *pTrunkNode;  //其实一个FDFSTrunkNode就指代一个trunk文件

struct fast_mblock_node *pMblockNode;(从上述free_blocks_man管理的free节点中取出一个节点)

pTrunkNode = (FDFSTrunkNode *)pMblockNode->data;

typedef struct tagFDFSTrunkNode {FDFSTrunkFullInfo trunk;    //trunk文件 infostruct fast_mblock_node *pMblockNode;   //for freestruct tagFDFSTrunkNode *next;} FDFSTrunkNode;

(3)FDFSTrunkFullInfo trunk;

typedef struct tagFDFSTrunkFullInfo {char status;  //normal or holdFDFSTrunkPathInfo path;FDFSTrunkFileInfo file;} FDFSTrunkFullInfo

4、trunk_alloc_space

int trunk_alloc_space(const int size, FDFSTrunkFullInfo *pResult){FDFSTrunkSlot target_slot;FDFSTrunkSlot *pSlot;FDFSTrunkNode *pPreviousNode;FDFSTrunkNode *pTrunkNode;int result;STORAGE_TRUNK_CHECK_STATUS();target_slot.size = (size > g_slot_min_size) ? size : g_slot_min_size;target_slot.head = NULL;pPreviousNode = NULL;pTrunkNode = NULL;pthread_mutex_lock(&trunk_mem_lock);while (1){pSlot = (FDFSTrunkSlot *)avl_tree_find_ge(tree_info_by_sizes \ + pResult->path.store_path_index, &target_slot);if (pSlot == NULL){break;}pPreviousNode = NULL;pTrunkNode = pSlot->head;while (pTrunkNode != NULL && \pTrunkNode->trunk.status == FDFS_TRUNK_STATUS_HOLD){pPreviousNode = pTrunkNode;pTrunkNode = pTrunkNode->next;}if (pTrunkNode != NULL){break;}target_slot.size = pSlot->size + 1;}if (pTrunkNode != NULL)  {if (pPreviousNode == NULL){pSlot->head = pTrunkNode->next;if (pSlot->head == NULL){trunk_delete_size_tree_entry(pResult->path. \store_path_index, pSlot);}}else{pPreviousNode->next = pTrunkNode->next;}trunk_free_block_delete(&(pTrunkNode->trunk));}else{pTrunkNode = trunk_create_trunk_file(pResult->path. \store_path_index, &result); //第一次执行时。最终是要获得一个pTrunkNode(相当于一个文件)。if (pTrunkNode == NULL){pthread_mutex_unlock(&trunk_mem_lock);return result;}}pthread_mutex_unlock(&trunk_mem_lock);result = trunk_split(pTrunkNode, size); //未跟踪,猜测:由于这是一个trunk file(64M),我们要写的文件远小于这个大小,当然应该将要写入的和剩余的部分分开。剩余的部分重新放入到free block中去(trunk_add_free_block)。if (result != 0){return result;}pTrunkNode->trunk.status = FDFS_TRUNK_STATUS_HOLD;//将状态从free改为hold。result = trunk_add_free_block(pTrunkNode, true); //将这个要写的文件块也加入free block中(同时写trunk binlog),用于之后分配。if (result == 0){memcpy(pResult, &(pTrunkNode->trunk), \sizeof(FDFSTrunkFullInfo)); //其中设置好了即将写的trunk文件的id,offset,要写的大小。注意这里的pResult即&(pFileContext->extra_info.upload.trunk_info),也就是说这个地址已经记录在pFileContext中了。之后写文件就是用这个trunk node(状态为hold),写完后会trunk_delete_space删掉}return result;}

最后,有了文件trunk文件的id、offset、要写的大小,之后的写文件就很简单了(与非trunk文件的写类似)。开启了truck线程后,同步线程也变成了两个,一个是同步文件(与之前相同),一个是同步trunk binlog(trunk file在split时,及块分配时,都会产生binlog;有了binlog同步,才能防止trunk server单点故障。)

0 0