Maison  >  Article  >  développement back-end  >  Créer un moteur de recherche en texte intégral hautes performances dans Go

Créer un moteur de recherche en texte intégral hautes performances dans Go

Linda Hamilton
Linda Hamiltonoriginal
2024-11-02 09:44:31906parcourir

1. Présentation

Dans le monde d’aujourd’hui, où de grandes quantités d’informations sont constamment générées, il est essentiel d’accéder efficacement aux données pertinentes. Les moteurs de recherche en texte intégral permettent une récupération rapide des données en indexant le contenu textuel, constituant ainsi l'épine dorsale d'applications allant des moteurs de recherche aux outils d'analyse de données. Compte tenu des ensembles de données massifs impliqués, les moteurs de recherche nécessitent une approche sophistiquée de l'indexation et des requêtes pour des performances optimales.

Ce blog vous guidera dans la création d'un moteur de recherche en texte intégral à l'aide de Go, en se concentrant sur des concepts avancés tels que le streaming de données, le multithreading et les structures d'indexation efficaces. Vous verrez comment gérer et rechercher dans de grands ensembles de données, en particulier des résumés Wikipédia, de manière efficace en termes de mémoire. En suivant ce guide, vous découvrirez comment tirer parti du modèle de concurrence de Go et son adéquation aux applications hautes performances.


2. Pile technologique

La pile technologique de ce projet inclut Go comme langage de programmation principal, sélectionné pour sa syntaxe simple, sa bibliothèque standard robuste et sa prise en charge native de la concurrence. Voici une liste des outils et bibliothèques essentiels :

  • Langage de programmation : Go (Golang)

    • Go offre un environnement efficace pour les applications simultanées avec des outils permettant de gérer plusieurs tâches sans sacrifier les performances.
  • Bibliothèques :

    • Analyse Gzip et XML : ceux-ci sont essentiels au traitement des données XML compressées de Wikipédia. Les bibliothèques standard d'encodage/xml et compress/gzip permettent une analyse et une décompression simples, s'intégrant bien à l'écosystème de Go.
    • Package de synchronisation : ce package Go de base est utilisé pour gérer les processus simultanés avec des constructions telles que sync.WaitGroup pour coordonner les goroutines et sync.Mutex pour gérer l'accès aux données.
    • kljensen/snowball : cette bibliothèque fournit des racines pour les jetons, permettant une meilleure optimisation de la recherche en réduisant les mots à leurs formes de base.
  • Source de données :

    • Le projet utilise des résumés Wikipédia, un fichier XML compressé contenant des résumés d'articles Wikipédia. Cet ensemble de données est suffisamment diversifié et vaste pour servir de test pratique des capacités du moteur de recherche. Télécharger ici

3. Racine de l'idée

Énoncé du problème

Avec des volumes de données toujours croissants, récupérer efficacement des informations significatives constitue un défi de taille. Les moteurs de recherche doivent gérer et accéder rapidement à de vastes ensembles de données textuelles, un problème qui a conduit à des innovations telles que les index inversés, la tokenisation et la normalisation des données.

Inspiration et recherche

Des outils populaires comme Elasticsearch démontrent la puissance d'un moteur de recherche en texte intégral construit sur des techniques robustes d'indexation et de récupération. Inspiré de ces moteurs standards de l’industrie, ce projet cherche à implémenter une solution similaire dans Go. Les fonctionnalités de simplicité, de performances et de concurrence de Go le rendent bien adapté à cette tâche, offrant la possibilité d'explorer les concepts utilisés par les principaux moteurs de recherche et de les adapter à une implémentation personnalisée.

Utilisateurs prévus

Ce projet est conçu pour ceux qui souhaitent comprendre le fonctionnement des moteurs de recherche sous le capot, ainsi que pour les développeurs et les passionnés désireux d'explorer le modèle de concurrence de Go. En offrant une expérience pratique, c'est l'occasion de comprendre comment Go peut gérer des tâches intensives telles que l'indexation et la recherche en temps réel, en particulier pour ceux qui s'intéressent au développement backend et full-stack.


4. Raisons de construire ce projet

Apprentissage pratique

Ce projet propose une approche pratique pour maîtriser le streaming et le multithreading dans Go, ainsi que pour plonger dans le fonctionnement des moteurs de recherche en texte intégral. Il permet d'expérimenter l'indexation, la tokenisation et le traitement de documents, offrant ainsi une compréhension complète des composants internes des moteurs de recherche.

Efficacité en Go

En utilisant Go, vous explorez sa haute efficacité de concurrence. Go est bien adapté à la création d'applications nécessitant l'exécution de plusieurs tâches en parallèle, ce qui en fait un langage idéal pour les objectifs axés sur les performances de ce projet.

Améliorer les compétences de Go

Ce projet développe des compétences avancées en Go, un langage largement utilisé dans les applications cloud natives et évolutives. Il offre une exposition à la mise en œuvre de solutions multithreading et de concurrence tout en mettant en évidence l'approche unique de Go en matière de gestion de la mémoire et des performances dans les applications à forte demande.


5. Le processus de travail et les concepts clés

Présentation du flux de travail

Le moteur suit un flux de travail structuré comprenant plusieurs étapes :

  1. Chargement de documents : les documents sont chargés et décompressés à partir du fichier XML en streaming, minimisant ainsi l'utilisation de la mémoire.
  2. Tokénisation et traitement de texte : chaque document est tokenisé, avec le texte normalisé par conversion en minuscules, suppression des mots vides et application de radicaux.
  3. Construction d'index : les jetons traités sont stockés dans un index inversé, mappant chaque jeton aux ID de document le contenant.
  4. Enregistrement/Chargement de l'index : L'index final peut être enregistré et chargé à partir du disque, préservant ainsi le travail d'indexation pour les sessions futures et accélérant l'initialisation du moteur de recherche.

Building a High-Performance Full-Text Search Engine in Go

Streaming et traitement des données

Le streaming permet de traiter les documents un par un sans charger l'intégralité de l'ensemble de données en mémoire. La fonction LoadDocuments gère la décompression et l'analyse en temps réel, en alimentant chaque document dans un canal. Cette configuration garantit que le système gère de grands ensembles de données en traitant les données de manière séquentielle, réduisant ainsi la charge de mémoire.

Concurrence dans le traitement des documents

Le traitement des documents est simultané, avec plusieurs goroutines responsables de l'analyse, de l'analyse et de l'indexation des documents. Cette simultanéité accélère considérablement le processus d'indexation et permet des mises à jour de recherche en temps réel.


6. Brève introduction au streaming et au multithreading

Streaming en Go

Définition et objectif

Le streaming est une technique dans laquelle les données sont traitées par morceaux au fur et à mesure qu'elles deviennent disponibles, plutôt que de les charger toutes en même temps. Ceci est particulièrement utile pour les grands ensembles de données où le chargement de l'intégralité de l'ensemble de données n'est pas pratique en raison des limitations de mémoire.

Avantages pour les grands ensembles de données

Le streaming permet de gérer efficacement la mémoire en ne traitant qu'une petite partie des données à un moment donné, ce qui est idéal pour ce moteur de recherche. Le système n’a pas besoin de charger tous les résumés Wikipédia en même temps ; au lieu de cela, il traite chaque document individuellement selon un flux constant.

Exemple de mise en œuvre

La fonction LoadDocuments charge et décompresse les documents en streaming, en utilisant les bibliothèques d'encodage/xml et de compression/gzip de Go pour analyser et envoyer chaque document à un canal de traitement.

Multithreading en Go

Définition et concepts de base

Le multithreading permet l'exécution simultanée de segments de code, augmentant ainsi les performances des applications en exécutant plusieurs opérations à la fois. Le modèle de concurrence natif de Go, avec des goroutines et des canaux, fournit un moyen simple de réaliser le multithreading.

Concurrence dans Go

La concurrence dans Go est obtenue à l'aide de goroutines, qui sont des threads légers qui permettent à plusieurs fonctions de s'exécuter simultanément. Les canaux permettent la communication entre les goroutines, garantissant que les données peuvent être transmises en toute sécurité sans avoir besoin d'une synchronisation complexe.

Comment il est utilisé ici

Dans ce moteur de recherche, plusieurs goroutines gèrent simultanément le traitement et l'indexation des documents. Par exemple, la fonction AddStreamed lit un canal de documents et indexe chacun d'eux simultanément, ce qui permet une indexation plus rapide sur de grands ensembles de données.

Défis et optimisations

La gestion de plusieurs threads peut entraîner des problèmes tels que des conditions de concurrence, où plusieurs threads accèdent simultanément aux ressources partagées. Le package de synchronisation de Go, avec Mutex et WaitGroup, permet d'éviter ces problèmes en synchronisant l'accès aux données et en garantissant que les tâches sont terminées avant de passer à l'étape suivante.


Fonctionnalité et caractéristiques du moteur de recherche en texte intégral

Ce moteur de recherche en texte intégral exploite les capacités de concurrence de Go pour créer un mécanisme d'indexation et de recherche performant. En utilisant le streaming de données et le multithreading, l'application traite efficacement de grands ensembles de données, tels que les résumés Wikipédia, sans surcharger la mémoire. Cette section explique les principales fonctions, fonctionnalités et méthodes clés utilisées dans le code.


1. Fonctionnalités principales du moteur de recherche

  • Indexation efficace : utilise un index inversé pour permettre une récupération rapide des documents correspondant à un terme de requête.
  • Traitement simultané : multithread les opérations d'indexation et de recherche de documents, permettant des opérations rapides et non bloquantes.
  • Stockage de documents avec métadonnées : stocke les métadonnées (telles que le titre et l'URL) aux côtés du contenu indexé, permettant ainsi la récupération des détails complets du document.
  • Persistance des index : les index peuvent être enregistrés et chargés à partir du disque, permettant des index de recherche réutilisables au fil des sessions.
  • Filtrage et normalisation des données : inclut la suppression des mots vides, la normalisation de la casse et la recherche de racines pour normaliser les jetons de recherche.

2. Composants clés et fonctionnalités

un. Chargement et diffusion de documents

La fonction LoadDocuments gère le chargement de documents à partir d'un fichier XML compressé, en le décompressant et en l'analysant sous forme de flux. Cette approche est économe en mémoire et particulièrement utile pour les grands ensembles de données.

Extrait de code : LoadDocuments

// LoadDocuments loads documents from a gzip-compressed XML file and sends them through a channel.
func LoadDocuments(path string, docChan chan<- Document) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()

    gz, err := gzip.NewReader(f)
    if err != nil {
        return err
    }
    defer gz.Close()

    dec := xml.NewDecoder(gz)
    dump := struct {
        Documents []Document `xml:"doc"`
    }{}

    if err := dec.Decode(&dump); err != nil {
        return err
    }

    for i, doc := range dump.Documents {
        doc.ID = i
        docChan <- doc
    }
    return nil
}

Ici :

  • Le fichier XML est décompressé et analysé lors de vos déplacements, ce qui signifie que l'intégralité du fichier n'est pas chargée en même temps.
  • Les documents sont ensuite diffusés vers un canal, docChan, ce qui leur permet d'être traités dès leur chargement, idéal pour une indexation simultanée.

b. Tokenisation et analyse de texte

Le fichier tokenizer.go comprend des fonctions permettant de normaliser et de standardiser le texte via la tokenisation, la normalisation de la casse, la suppression des mots vides et la radicalisation.

Extrait de code : analyser

// LoadDocuments loads documents from a gzip-compressed XML file and sends them through a channel.
func LoadDocuments(path string, docChan chan<- Document) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()

    gz, err := gzip.NewReader(f)
    if err != nil {
        return err
    }
    defer gz.Close()

    dec := xml.NewDecoder(gz)
    dump := struct {
        Documents []Document `xml:"doc"`
    }{}

    if err := dec.Decode(&dump); err != nil {
        return err
    }

    for i, doc := range dump.Documents {
        doc.ID = i
        docChan <- doc
    }
    return nil
}

Cette fonction :

  • Tokenise le texte en mots ou jetons individuels.
  • Convertit les jetons en minuscules pour garantir l'insensibilité à la casse.
  • Supprime les mots vides, réduisant ainsi les données inutiles dans l'index.
  • Rattache les jetons à leur forme racine, garantissant ainsi la cohérence de la recherche (par exemple, « en cours d'exécution » devient « exécuter »).

c. Création et gestion de l'index inversé

La structure Index est la structure de données de base, contenant l'index inversé et le magasin de documents. L'index inversé est une carte où chaque jeton (mot) correspond à une liste d'identifiants de documents contenant ce mot, permettant une recherche efficace.

Extrait de code : ajout de documents à l'index

// analyze analyzes the text and returns a slice of tokens.
func analyze(text string) []string {
    tokens := tokenize(text)
    tokens = lowercaseFilter(tokens)
    tokens = stopwordFilter(tokens)
    tokens = stemmerFilter(tokens)
    return tokens
}

La fonction AddDocument :

  • Verrouille l'index pour éviter les conditions de concurrence lors d'écritures simultanées.
  • Stocke les documents par ID dans docStore, permettant la récupération du texte intégral par ID.
  • Construit l'index inversé en traitant chaque jeton du document et en ajoutant son ID à la liste des jetons, garantissant une recherche rapide.

Stockage et récupération d'index

Pour permettre une utilisation persistante de l'index, les méthodes Save et Load dans index.go utilisent le package encoding/gob de Go pour la sérialisation et la désérialisation.

// AddDocument adds a single document to the index.
func (idx *Index) AddDocument(doc Document) {
    idx.mu.Lock()
    defer idx.mu.Unlock()

    idx.docStore[doc.ID] = doc
    for _, token := range analyze(doc.Text) {
        ids := idx.index[token]
        if ids != nil && ids[len(ids)-1] == doc.ID {
            continue
        }
        idx.index[token] = append(ids, doc.ID)
    }
}

d. Indexation simultanée de documents avec streaming

À l'aide de la méthode AddStreamed, les documents de docChan sont indexés simultanément. Plusieurs goroutines gèrent le processus d'ajout de documents, accélérant considérablement l'indexation des grands ensembles de données.

Extrait de code : AddStreamed

// Save serializes both the index and docStore to a file.
func (idx *Index) Save(filePath string) error {
    idx.mu.RLock()
    defer idx.mu.RUnlock()

    file, err := os.Create(filePath)
    if err != nil {
        return err
    }
    defer file.Close()

    encoder := gob.NewEncoder(file)
    if err := encoder.Encode(idx.index); err != nil {
        return err
    }
    if err := encoder.Encode(idx.docStore); err != nil {
        return err
    }

    return nil
}

Cette méthode :

  • Fait tourner plusieurs goroutines pour traiter les documents en parallèle.
  • Utilise un WaitGroup pour attendre que toutes les goroutines soient terminées, garantissant que tous les documents sont traités avant de continuer.

e. Recherche de documents

La fonction de recherche d'index.go permet de récupérer efficacement les identifiants de documents correspondant à une requête de recherche en recherchant les documents contenant tous les jetons de requête.

Extrait de code : recherche

// AddStreamed adds documents from a channel to the index concurrently.
func (idx *Index) AddStreamed(docChan <-chan Document) {
    var wg sync.WaitGroup
    numWorkers := 4 // Number of concurrent workers

    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for doc := range docChan {
                idx.AddDocument(doc)
            }
        }()
    }
    wg.Wait()
}

La fonction Recherche :

  • Analyse le texte de la requête en jetons, puis vérifie si chaque jeton existe dans l'index.
  • Trouve l'intersection des identifiants pour chaque jeton, en renvoyant uniquement les documents contenant tous les termes de la requête.

Affichage des résultats de recherche

La méthode PrintResultsTable formate et affiche les ID de document correspondants avec des titres et des extraits de texte pour plus de lisibilité.

// LoadDocuments loads documents from a gzip-compressed XML file and sends them through a channel.
func LoadDocuments(path string, docChan chan<- Document) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()

    gz, err := gzip.NewReader(f)
    if err != nil {
        return err
    }
    defer gz.Close()

    dec := xml.NewDecoder(gz)
    dump := struct {
        Documents []Document `xml:"doc"`
    }{}

    if err := dec.Decode(&dump); err != nil {
        return err
    }

    for i, doc := range dump.Documents {
        doc.ID = i
        docChan <- doc
    }
    return nil
}

Cette vue tableau est utile pour une vérification rapide et la lisibilité des résultats, car elle comprend un extrait du texte de chaque document correspondant.


7. Portée future

Ce moteur de recherche en texte intégral constitue une base solide pour créer un système de recherche complet, mais plusieurs améliorations pourraient le rendre encore plus puissant et riche en fonctionnalités :

1. Traitement distribué

  • Objectif : faire évoluer le moteur de recherche pour gérer un volume de données encore plus important en répartissant la charge de travail sur plusieurs machines.
  • Mise en œuvre : en répartissant l'indexation et les requêtes de documents sur les serveurs, le moteur de recherche peut gérer davantage de requêtes et des ensembles de données plus volumineux. Des technologies telles que gRPC ou HTTP/2 pourraient faciliter une communication efficace entre les nœuds distribués.

2. Support avancé des requêtes

  • Objectif : permettre aux utilisateurs d'effectuer des recherches plus sophistiquées à l'aide d'opérateurs (par exemple, AND, OR, NOT) et de requêtes de proximité.
  • Mise en œuvre : étendez l'algorithme d'indexation pour prendre en charge les requêtes complexes, telles que les expressions exactes et les recherches par caractères génériques, améliorant ainsi la flexibilité de la recherche.

3. Mises à jour de l'index en temps réel

  • Objectif : Permettre au moteur de mettre à jour les index de manière dynamique à mesure que de nouveaux documents sont ajoutés.
  • Mise en œuvre : une fonctionnalité d'indexation en temps réel permettrait d'ajouter de nouveaux documents sans nécessiter une réindexation complète, ce qui la rendrait idéale pour les applications qui gèrent du contenu fréquemment mis à jour.

4. Intégration du Machine Learning pour le classement

  • Objectif : Améliorer la pertinence des résultats en incorporant des modèles d'apprentissage automatique pour classer les documents en fonction du comportement et de la pertinence des utilisateurs.
  • Mise en œuvre : en analysant les données de recherche antérieures et les préférences des utilisateurs, le moteur pourrait donner la priorité aux documents plus pertinents, rendant les résultats de recherche plus précis et personnalisés.

5. Traitement du langage naturel (NLP) amélioré

  • Objectif : utiliser le NLP pour améliorer la prise en charge de la tokenisation, de la recherche de radicaux et des synonymes, permettant ainsi au moteur de traiter les requêtes des utilisateurs de manière plus intuitive.
  • Mise en œuvre : L'exploitation des techniques de PNL améliorerait la correspondance des requêtes en tenant compte des synonymes, des pluriels et du contexte, améliorant ainsi la capacité du moteur à interpréter l'intention de l'utilisateur.

8. Capture d'écran des résultats

Building a High-Performance Full-Text Search Engine in Go


9. Conclusion

Construire un moteur de recherche en texte intégral à l'aide de Go est un projet pratique pour comprendre des concepts de programmation complexes tels que la concurrence, le multithreading et le streaming de données. Ce projet démontre la capacité de Go à gérer efficacement de grands ensembles de données tout en maintenant des performances élevées. En se concentrant sur une indexation efficace et un traitement multithread, ce moteur de recherche atteint une vitesse et une efficacité de mémoire impressionnantes.

Grâce à ce processus, nous avons exploré les composants essentiels des moteurs de recherche (streaming, tokenisation, indexation inversée et multithreading) et vu comment ces éléments s'assemblent pour créer une solution de recherche réactive et soucieuse des ressources. Avec des améliorations potentielles telles que le traitement distribué et l'intégration NLP, ce moteur de recherche peut encore évoluer, offrant des capacités encore plus grandes.

L'expérience acquise ici met non seulement en valeur les performances de Go, mais sert également de base pour créer des applications réelles et évolutives capables de répondre aux exigences des environnements gourmands en données.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn