Lors de l'écriture de fichiers importants, il est nécessaire de prendre en compte l'arrêt inattendu du système d'exploitation, etc. Si vous ne savez pas comment faire, des fichiers semi-finis et des fichiers vides seront générés, ce qui sera fatal au démarrage du système et dans le système lié.
Le langage C / Java / Python / JavaScript (node.js) est donné à titre d'exemple, mais il est nécessaire de prendre des mesures dans presque tous les langages.
Un problème fatal s'est produit dans lequel le logiciel en production n'a pas démarré.
Lorsque j'ai collecté et analysé les journaux et les fichiers de configuration, les fichiers de configuration étaient complètement corrompus.
Le fichier de configuration est lu au démarrage, mais peut être écrit si nécessaire. En suivant le code, je remarque qu'il peut être écrit à mi-chemin s'il est interrompu de force pendant le processus d'écriture.
L'alimentation est coupée lorsqu'elle est épuisée et la synchronisation peut s'être miraculeusement chevauchée.
Si vous l'écrivez directement dans le fichier de configuration, vous pouvez obtenir un état semi-fini (par exemple, lorsque vous voulez écrire 10 caractères, vous n'avez toujours qu'un caractère), même si cela prend peu de temps. Une fois que vous avez fini d'écrire dans config.yml.tmp, puis que vous le renommez en config.yml, tout peut être mis à jour ou non ou de manière atomique!
Il sera publié avec entière satisfaction, mais il y a un problème qu'il ne recommencera pas. Cette fois, le fichier de configuration est de 0 Ko. Si l'écriture du fichier tmp échoue, il ne sera pas renommé, il ne devrait donc jamais être de 0 Ko sur le flux. La couleur est explicite et la fermeture est soignée.
J'ai trouvé un élément en feuilletant le livre O'Reilly sur la programmation système Linux que j'avais sous la main.
Quoi quoi··? Ecrire le tampon sale sur le disque ...?
À ce stade, je remarque enfin l'erreur et la solution. Non, je l'ai appris en tant que connaissance, mais cela n'est pas sorti.
Même si le processus d'écriture est programmé, le fichier n'est pas écrit immédiatement et est temporairement sauvegardé au format d'un tampon sale.
Le processus d'exportation de disque est extrêmement lent, donc si vous exportez à chaque fois, le programme sera foiré (100 fois ou plus ou à ce niveau). Pour éviter cela, c'est une technique qui a toujours été adoptée dans les systèmes de fichiers récents. Avec cette approche, le processus sous-traite le traitement d'écriture lente au système d'exploitation et se permet de passer à autre chose. Le moment où le système d'exploitation écrit sur le disque dépend du système de fichiers avec lequel le système d'exploitation interagit. Dans certains cas, cela peut prendre 10 secondes ou plus, et il y a plus que suffisamment de temps pour corrompre le fichier.
Si le système d'exploitation tombe en panne en raison d'une panne de courant inattendue dans cet état, un fichier endommagé ou un fichier vide sera créé même s'il est vidé ou fermé dans le flux du programme. En d'autres termes, config.yml.tmp lui-même était à moitié terminé, donc même s'il était renommé config.yml, il était à moitié terminé.
L'appel de fsync () écrit immédiatement le tampon sale sur le disque, ce qui est terminé avant de passer à l'étape suivante.
Sous Linux, un fichier comprend les deux types de données suivants.
--Métadonnées appelées inode
L'inode est la date et l'heure de la modification du fichier, ou les données affichées lors de sa gestion dans un répertoire.
fsync () écrit les deux et fdatasync () écrit uniquement les données du fichier lui-même.
Si le fichier n'est pas mis à jour très souvent ou que vous n'avez pas à vous soucier des performances, vous pouvez utiliser fsync ().
Utilisez fdatasync () si l'inœud (c'est-à-dire les métadonnées telles que l'heure de la dernière mise à jour) ne doit pas être mis à jour dans le pire des cas, ou s'il est mis à jour fréquemment et que les performances sont un problème.
Lors de la génération de fichiers importants, vous pouvez écrire de force sur le disque afin d'être assuré qu'il y aura un arrêt inattendu. Sous Linux, l'arrêt en suivant la procédure habituelle n'est pas un problème.
Voici quelques exemples spécifiques de code. Une certaine gestion des erreurs est omise.
sample.c
int main() {
const char* tmp_file_path = "./fsync_test.txt";
FILE* fp= fopen(tmp_file_path , "w");
int fd = fileno(fp);
fputs("fsync() test\n", fp);
fflush(fp);
//C'est le point!!
int fsync_ret = fsync(fd);
fclose(fp);
return fsync_ret;
}
FsyncTest.java
import java.io.File;
import java.io.FileOutputStream;
public class FsyncTest {
public static void main(String[] args) throws Exception {
File file = new File("./fsync.txt");
try (FileOutputStream output = new FileOutputStream(file);) {
output.write("Fsync() test\n".getBytes("UTF-8"));
//C'est le point!!
output.getFD().sync();
}
}
}
sample.py
import os
with open('./fsync_test.txt', 'w') as f:
f.write('fsync() test')
f.flush() #Tampon sale avec juste ça
#C'est le point!!
os.fsync(f.fileno())
fsync.js
const http = require('http');
const server = http.createServer((request, response) => {
const fs = require('fs');
fs.open('./fsync_test.txt', 'w', (err, fd) => {
fs.write(fd, 'fsync() test\n', () => {
//C'est le point!!
fs.fsyncSync(fd);
response.writeHead(200, {'Content-Type': 'text/plain'})
response.end('Write success\n');
fs.close(fd, ()=>{});
})
})
})
server.listen(9000);
Le fichier n'est plus corrompu.
À propos, le temps d'être un tampon sale est assez long. Selon le système de fichiers, cela peut prendre environ 30 secondes. Avant la contre-mesure, dans Windows 7 à portée de main, j'ai écrit un fichier, l'ai ouvert avec un éditeur de texte, confirmé que le contenu avait été écrit, débranché l'alimentation après 15 secondes et le fichier était cassé après le démarrage Était là. Quand je l'ai essayé dans l'environnement CentOS 6, le résultat était presque le même.
Après les mesures, il ne s'est pas cassé même immédiatement après l'écriture.
Quel que soit le langage, fsync () ou un traitement équivalent est essentiel lors de la génération de fichiers importants. Cependant, cela force des exportations de disques synchrones extrêmement lentes, ce qui peut avoir un impact sérieux sur les performances dans certaines conditions. C'est mal de le faire dans les nuages sombres parce que c'est sûr.
Vous avez fourni un lien dans les commentaires qui est très pertinent pour cet article.
https://www.atmarkit.co.jp/flinux/rensai/watch2009/watch05a.html
C'est un problème causé par la lenteur de fsync (). C'est un article selon lequel l'utilisation de fdatasync () accélère d'autant la mise à jour de l'inode.
https://masahikosawada.github.io/2019/02/17/PostgreSQL-fsync-issue/ Le problème est que si fsync () échoue, vous ne pouvez plus appeler fsync (). Dans PostgreSQL, si fsync () échoue, planter la base de données et à partir du journal des transactions (WAL) Il semble qu'il ait fait une correction pour le restaurer.
Je me suis demandé si cela échouerait en premier lieu, mais il semble que cela se produise facilement dans SAN et NFS. Si vous écrivez dans un système général, vous devez conserver les données à write () et recommencer à partir de write ().
fsync () est une fonction de bibliothèque Linux et ne peut pas être utilisée sous Windows. Sous Windows, cela peut être réalisé en utilisant l'API suivante.
BOOL FlushFileBuffers(HANDLE hFile);
En outre, dans Windows 7, cela peut être réalisé en définissant l'ensemble du système d'exploitation. [1] Ouvrez le panneau de configuration [2] Ouvrez le Gestionnaire de périphériques [3] Sélectionnez un disque dans le lecteur de disque et ouvrez les propriétés [4] Dans l'onglet Stratégie, décochez "Activer le cache d'écriture du périphérique"
Notez que cette méthode ralentira toutes les opérations, pas seulement l'application.
Recommended Posts