-
Notifications
You must be signed in to change notification settings - Fork 0
03_chunk_storage___management.md
Welcome back to cdfs-ton! In Chapter 1: Client Application Interface, you learned how to use simple commands like put and get. In Chapter 2: Metadata Management, we explored the "control tower" (the Metadata Server) that keeps track of all your file information, like their names, how they're broken into chunks, and which storage locations hold those chunks.
But here's the big question: where are these "storage locations"? And how do they actually store and retrieve the physical pieces of your files?
This is where Chunk Storage & Management comes in! This chapter focuses on the "workhorses" of cdfs-ton – the Storage Nodes. Imagine them as individual, dedicated storage lockers or shelves in our giant library. They don't know the full story of your file (that's the Metadata Server's job), but they are experts at handling the individual chunks of data they are assigned.
When you put a file into cdfs-ton, your client application (after getting instructions from the Metadata Server) sends the file's chunks directly to various Storage Nodes. Each Storage Node then saves its assigned chunks onto its own local disk.
Similarly, when you get a file, your client asks the Metadata Server where to find its chunks. Then, your client directly contacts the relevant Storage Nodes, which then retrieve and send those chunks back to your client.
In short, Storage Nodes are responsible for:
- Storing Chunks: Physically saving file chunks to their local disk.
- Retrieving Chunks: Finding and sending requested chunks back to clients or other nodes.
- Reporting: Regularly telling the Metadata Server which chunks they hold and their overall health.
- Replicating: Making copies of chunks and sending them to other Storage Nodes for safety.
Let's follow a chunk of your my_vacation.jpg file as it interacts with a Storage Node.
When your cdfs_client needs to store a chunk:
-
Client Connects: Your
cdfs_clientconnects directly to a chosen Storage Node. -
Send Request & Data: It sends a message (an
OP_STORE_CHUNKrequest) to the Storage Node, saying, "Here's chunk ID 123, it's this big, and here's its data." It also sends a "checksum" (a unique digital fingerprint of the data) to make sure the chunk isn't corrupted during transfer. - Save to Disk: The Storage Node receives the chunk data and saves it as a file on its local hard drive. It also saves the checksum separately.
- Confirm Storage: The Storage Node replies to the client, saying, "Got it! Chunk 123 is safely stored."
When your cdfs_client needs to retrieve a chunk:
-
Client Connects: Your
cdfs_clientconnects directly to a Storage Node known to hold chunk ID 123. -
Send Request: It sends a message (an
OP_LOAD_CHUNKrequest) to the Storage Node, saying, "Please give me chunk ID 123." - Load from Disk: The Storage Node finds chunk ID 123 on its local disk. It also reads the stored checksum.
- Send Data & Checksum: The Storage Node sends the chunk data and its checksum back to the client.
- Confirm Retrieval: The Storage Node replies, "Here's your chunk!"
Storage Nodes don't just passively store data. They actively communicate with the Metadata Server:
- Heartbeats: Every few seconds, each Storage Node sends a "heartbeat" message to the Metadata Server, saying, "I'm alive and well!" This tells the Metadata Server which nodes are active.
- Block Reports: Periodically, Storage Nodes also send a "block report" to the Metadata Server. This is a list of all the chunk IDs they currently hold. This is how the Metadata Server can update its records and know exactly where every single chunk is stored.
sequenceDiagram
participant C as CDFS Client
participant MS as Metadata Server
participant SN1 as Storage Node 1
participant SN2 as Storage Node 2
Note over C,SN2: Initial setup (from previous chapters)
C->>MS: 1. Request file metadata for "my_vacation.jpg"
MS-->>C: 2. Here are chunk IDs and their locations (e.g., Chunk 1 is on SN1, SN2)
C->>SN1: 3. Request Chunk 1 data (OP_LOAD_CHUNK)
SN1->>SN1: 4. Read Chunk 1 from local disk
SN1-->>C: 5. Send Chunk 1 data & checksum
C->>SN2: 6. Request Chunk 2 data (OP_LOAD_CHUNK)
SN2->>SN2: 7. Read Chunk 2 from local disk
SN2-->>C: 8. Send Chunk 2 data & checksum
Note over C: Client reconstructs file from all chunks
Note over SN1,SN2: Background operations
loop every few seconds
SN1->>MS: Heartbeat (I'm alive!)
SN2->>MS: Heartbeat (I'm alive!)
end
loop periodically
SN1->>MS: Block Report (List of chunks I hold)
SN2->>MS: Block Report (List of chunks I hold)
end
The magic of Storage Nodes happens primarily in two files:
-
storage_node/storage.c: This file contains the core logic for how chunks are saved to and loaded from the local disk. It's like the detailed instructions for managing the contents of each locker. -
storage_node/storage_node.c: This file contains the main program for the Storage Node, including how it listens for network requests (from clients or the Metadata Server) and dispatches them to thestorage.cfunctions. It's like the main interface for the entire storage locker facility.
Let's dive into some key functions!
Before a Storage Node can store anything, it needs a place on its disk. The storage_init function handles this.
// File: storage_node/storage.c
static uint8_t g_storage_dir[256] = "./storage_data";
void storage_init(const uint8_t *dir) {
strncpy((char *)g_storage_dir, (const char *)dir, sizeof(g_storage_dir) - 1);
g_storage_dir[sizeof(g_storage_dir) - 1] = '\0';
mkdir((const char *)g_storage_dir, 0755);
}-
g_storage_dir: This is a global variable that holds the path to the directory where all chunks will be stored (e.g.,./storage_data). -
storage_init: This function takes a directory path (dir) and usesmkdirto create that directory if it doesn't already exist. This ensures the Storage Node has a dedicated place for its chunks.
This is the core function a Storage Node uses to receive chunk data over the network and save it to its local disk.
// File: storage_node/storage.c (simplified)
int32_t store_chunk_stream(int32_t chunk_id, int32_t sockfd, size_t size, uint32_t expected_checksum) {
char path[512];
// Create a unique file name for the chunk data
snprintf(path, sizeof(path), "%s/chunk_%d.dat", (char *)g_storage_dir, chunk_id);
FILE *fp = fopen(path, "wb"); // Open file for writing (binary mode)
if (!fp) return -1; // Error opening file
uint8_t buf[8192];
size_t remaining = size;
uint32_t calc_chk = CHKSUM_INIT; // Initialize checksum
// Loop to receive data from the network and write to file
while (remaining > 0) {
size_t to_read = remaining < sizeof(buf) ? remaining : sizeof(buf);
if (recv_exact(sockfd, buf, to_read) != 0) { // Receive bytes from socket
fclose(fp); remove(path); return -1; // Error, clean up
}
fwrite(buf, 1, to_read, fp); // Write bytes to file
calc_chk = update_checksum(calc_chk, buf, to_read); // Update checksum
remaining -= to_read;
}
fclose(fp);
if (calc_chk != expected_checksum) { // Check if data was corrupted
LOG_WARN("SN", "Checksum mismatch for chunk %d!\n", chunk_id);
remove(path); return -1; // Mismatch, delete corrupted file
}
// Save the checksum in a separate .crc file
snprintf(path, sizeof(path), "%s/chunk_%d.crc", (char *)g_storage_dir, chunk_id);
fp = fopen(path, "wb");
if (fp) { fwrite(&expected_checksum, sizeof(uint32_t), 1, fp); fclose(fp); }
return 0; // Success
}-
chunk_%d.dat: Each chunk's data is saved in a file namedchunk_ID.dat(e.g.,chunk_123.dat). -
fopenandfwrite: These standard C functions are used to open a file and write the received network data into it. -
recv_exact: This helper function (fromcommon/serialization.h) ensures that exactlyto_readbytes are received from the network socket (sockfd), preventing incomplete data. -
Checksum Verification: After all data is received, the Storage Node calculates its own checksum (
calc_chk) and compares it to theexpected_checksumsent by the client. If they don't match, the data is considered corrupted, and the chunk file is deleted. This is crucial for data integrity! -
chunk_%d.crc: The correct checksum is saved in a separate file (e.g.,chunk_123.crc) alongside the data file.
This function allows a Storage Node to read a chunk from its local disk and send it over the network.
// File: storage_node/storage.c (simplified)
int32_t load_chunk_stream(int32_t chunk_id, int32_t sockfd, size_t file_size, uint32_t *out_checksum) {
char path[512];
// First, read the checksum from the .crc file
snprintf(path, sizeof(path), "%s/chunk_%d.crc", (char *)g_storage_dir, chunk_id);
FILE *fp = fopen(path, "rb");
if (fp) { fread(out_checksum, sizeof(uint32_t), 1, fp); fclose(fp); }
else { *out_checksum = 0; } // If no checksum file, assume 0
// Then, open and stream the chunk data from the .dat file
snprintf(path, sizeof(path), "%s/chunk_%d.dat", (char *)g_storage_dir, chunk_id);
fp = fopen(path, "rb");
if (!fp) return -1; // Error: chunk not found
uint8_t buf[8192];
size_t remaining = file_size;
// Loop to read data from file and send over network
while (remaining > 0) {
size_t to_read = remaining < sizeof(buf) ? remaining : sizeof(buf);
size_t bytes = fread(buf, 1, to_read, fp); // Read bytes from file
if (bytes == 0) break; // End of file
if (send_exact(sockfd, buf, bytes) != 0) { // Send bytes over socket
fclose(fp); return -1; // Error sending
}
remaining -= bytes;
}
fclose(fp);
if (remaining > 0) return -1; // Incomplete file
return 0; // Success
}-
Reading Checksum: The function first opens the
.crcfile to retrieve the stored checksum. -
freadandsend_exact: It then opens the.datfile, reads chunks of data usingfread, and sends them over the network socket (sockfd) usingsend_exact.
When a file is removed from cdfs-ton, the Metadata Server tells the relevant Storage Nodes to delete its chunks.
// File: storage_node/storage.c (simplified)
int32_t delete_chunk_files(int32_t chunk_id) {
char path[512];
snprintf(path, sizeof(path), "%s/chunk_%d.dat", (char *)g_storage_dir, chunk_id);
remove(path); // Delete the data file
snprintf(path, sizeof(path), "%s/chunk_%d.crc", (char *)g_storage_dir, chunk_id);
remove(path); // Delete the checksum file
return 0;
}-
remove: This standard C function is used to delete both the.datand.crcfiles associated with a givenchunk_id.
This function is used when a Storage Node needs to generate a "block report" to send to the Metadata Server. It scans its storage directory to find all the chunk files it currently holds.
// File: storage_node/storage.c (simplified)
int32_t scan_chunks(int32_t *out_ids, int32_t max_count) {
DIR *dir = opendir((char *)g_storage_dir); // Open the storage directory
if (!dir) return 0;
int32_t count = 0;
struct dirent *entry;
// Loop through each item in the directory
while ((entry = readdir(dir)) != NULL && count < max_count) {
int32_t chunk_id;
// Check if the filename matches "chunk_ID.dat" pattern
if (sscanf(entry->d_name, "chunk_%d.dat", &chunk_id) == 1) {
out_ids[count++] = chunk_id; // Add chunk ID to the list
}
}
closedir(dir); // Close the directory
return count; // Return total chunks found
}-
opendirandreaddir: These functions are used to list the contents of theg_storage_dir. -
sscanf: This function tries to extract a number (thechunk_id) from filenames that match thechunk_ID.datpattern. -
out_ids: This array will be filled with the IDs of all chunks found on the node, which is then sent to the Metadata Server in a block report.
This is a separate, continuous process within each Storage Node that handles its regular communication with the Metadata Server.
// File: storage_node/storage_node.c (simplified)
void* heartbeat_thread(void* arg) {
(void)arg; // Unused argument
while (1) { // Loop forever
// --- Send Heartbeat ---
int32_t sock = connect_to(g_config.meta_ip, g_config.meta_port);
if (sock >= 0) {
req_heartbeat_t hb = { storage_port, 1024 /*dummy space*/ };
net_header_t hdr = { OP_HEARTBEAT, sizeof(hb) };
send_exact(sock, &hdr, sizeof(hdr));
send_exact(sock, &hb, sizeof(hb));
close(sock);
}
// --- Send Block Report ---
sock = connect_to(g_config.meta_ip, g_config.meta_port);
if (sock >= 0) {
req_block_report_t report;
report.storage_port = storage_port;
// Use scan_chunks to get list of held chunks
report.chunk_count = scan_chunks(report.chunk_ids, MAX_CHUNKS);
net_header_t hdr = { OP_BLOCK_REPORT, sizeof(report) };
send_exact(sock, &hdr, sizeof(hdr));
send_exact(sock, &report, sizeof(report));
close(sock);
}
sleep(5); // Wait 5 seconds before repeating
}
return NULL;
}-
connect_to: A helper function to establish a network connection to the Metadata Server. -
OP_HEARTBEAT: This operation code signals a heartbeat message. It tells the Metadata Server that this Storage Node is active. -
OP_BLOCK_REPORT: This operation code signals a block report. Thescan_chunksfunction is called to get a list of all chunk IDs on this node, which is then sent to the Metadata Server. -
sleep(5): This causes the thread to pause for 5 seconds, ensuring heartbeats and block reports are sent regularly, but not constantly.
Sometimes, the Metadata Server might notice that a chunk doesn't have enough copies (replicas). It then instructs one Storage Node to copy that chunk to another. This is handled by the OP_REPLICATE_CHUNK operation.
// File: storage_node/storage_node.c (simplified handle_client function)
void handle_client(int32_t client_sock) {
net_header_t header;
recv_exact(client_sock, &header, sizeof(header)); // Read request header
// ... (other op_code handlers like OP_STORE_CHUNK, OP_LOAD_CHUNK) ...
if (header.op_code == OP_REPLICATE_CHUNK) {
req_replicate_chunk_t req;
recv_exact(client_sock, &req, sizeof(req)); // Read replication request
// This node (the source) loads the chunk data
// ... (find chunk_id, get its size and checksum from local disk) ...
// Connect to the destination Storage Node
int32_t dst_sock = connect_to(req.dst_ip, req.dst_port);
if (dst_sock >= 0) {
// Send an OP_STORE_CHUNK request to the destination node
req_store_chunk_t sreq = { req.chunk_id, st.st_size, checksum };
net_header_t shdr = { OP_STORE_CHUNK, (uint32_t)(sizeof(sreq) + st.st_size) };
send_exact(dst_sock, &shdr, sizeof(shdr));
send_exact(dst_sock, &sreq, sizeof(sreq));
// Stream the actual chunk data to the destination node
int32_t push_status = load_chunk_stream(req.chunk_id, dst_sock, st.st_size, &checksum);
// ... (receive confirmation from destination node) ...
close(dst_sock);
}
// ... (send response back to Metadata Server) ...
}
close(client_sock);
}- When a Storage Node receives an
OP_REPLICATE_CHUNKrequest from the Metadata Server, it acts as a "source" node. - It first reads the specified chunk from its own local disk using
load_chunk_stream. - Then, it connects to the destination Storage Node (whose IP and port are provided in the
req_replicate_chunk_tstructure). - It sends an
OP_STORE_CHUNKrequest to the destination node and streams the chunk data, effectively telling the destination node, "Hey, store this chunk!" - This shows how Storage Nodes can also communicate with each other, orchestrated by the Metadata Server, to maintain data replication.
The Storage Nodes are the physical backbone of cdfs-ton, the "storage lockers" that actually hold your file chunks. They efficiently store and retrieve data on their local disks, verify data integrity using checksums, and keep the Metadata Server updated on their status and holdings through heartbeats and block reports. Their ability to replicate chunks ensures your data remains safe and available, even if some nodes fail.
Now that we understand how clients interact with the system, how metadata is managed, and how chunks are stored, we need to understand the language they speak. In the next chapter, we'll explore the Network Communication Protocol that ties all these components together.