Rumah > pembangunan bahagian belakang > Golang > Bleve: Bagaimana untuk membina enjin carian pantas roket?

Bleve: Bagaimana untuk membina enjin carian pantas roket?

Susan Sarandon
Lepaskan: 2025-01-03 04:23:40
asal
1007 orang telah melayarinya

Bleve: How to build a rocket-fast search engine?

Go/Golang ialah salah satu bahasa kegemaran saya; Saya suka minimalisme dan betapa bersihnya, ia sangat padat dari segi sintaks dan berusaha keras untuk memastikan perkara mudah (saya peminat tegar prinsip KISS).

Salah satu cabaran utama yang saya hadapi sejak kebelakangan ini ialah membina enjin carian yang pantas. Pasti ada pilihan seperti SOLR dan ElasticSearch; kedua-duanya berfungsi dengan baik dan sangat berskala, walau bagaimanapun, saya perlu memudahkan carian, dengan menjadikannya lebih pantas dan lebih mudah untuk digunakan dengan sedikit atau tiada kebergantungan.

Saya perlu mengoptimumkan cukup untuk saya memulangkan hasil dengan cepat supaya keputusan itu boleh ditarafkan semula. Walaupun C/Rust mungkin sesuai untuk ini, saya menghargai kelajuan pembangunan dan produktiviti. Golang adalah yang terbaik dari kedua-dua dunia saya rasa.

Dalam artikel ini, saya akan melihat contoh mudah bagaimana anda boleh membina enjin carian anda sendiri menggunakan Go, anda akan terkejut: ia tidaklah sesulit yang anda fikirkan.

Golang: Python pada steroid

Saya tidak tahu mengapa, tetapi Golang berasa seperti Python dalam satu cara. Sintaksnya sangat mudah untuk difahami, mungkin kerana kekurangan koma bertitik dan tanda kurung di mana-mana atau kekurangan kenyataan cuba-tangkap yang jelek. Mungkin ia adalah pemformat Go yang hebat, saya tidak tahu.

Apa pun, memandangkan Golang menjana satu binari serba lengkap, ia sangat mudah untuk digunakan ke mana-mana pelayan pengeluaran. Anda hanya "pergi membina" dan tukar keluar yang boleh laku.

Itulah yang saya perlukan.

Adakah anda Bleve?

Tidak, itu bukan kesilapan menaip ?. Bleve ialah perpustakaan carian yang berkuasa, mudah digunakan dan sangat fleksibel untuk Golang.

Semasa sebagai pembangun Go, anda biasanya mengelakkan pakej pihak ketiga seperti wabak; kadang-kadang masuk akal untuk menggunakan pakej pihak ke-3. Bleve adalah pantas, direka bentuk dengan baik dan memberikan nilai yang mencukupi untuk membenarkan penggunaannya.

Selain itu, inilah sebab mengapa saya "Bleve":

  • Sendiri, salah satu kelebihan besar Golang ialah binari tunggal, jadi saya ingin mengekalkan rasa itu dan tidak memerlukan DB atau perkhidmatan luaran untuk menyimpan dan menanyakan dokumen. Bleve berjalan dalam ingatan dan menulis pada cakera yang serupa dengan Sqlite.

  • Mudah dipanjangkan. Memandangkan ia hanya kod Go, saya boleh mengubah suai perpustakaan atau memanjangkannya dengan mudah dalam pangkalan kod saya mengikut keperluan.

  • Pantas: Hasil carian merentas 10 juta dokumen mengambil masa hanya 50-100ms, ini termasuk penapisan.

  • Faceting: anda tidak boleh membina enjin carian moden tanpa beberapa tahap sokongan faceting. Bleve mempunyai sokongan penuh untuk jenis faset biasa: seperti julat atau kiraan kategori mudah.

  • Pengindeksan pantas: Bleve agak perlahan daripada SOLR. SOLR boleh mengindeks 10 juta dokumen dalam masa 30 minit, manakala Bleve mengambil masa lebih sejam, bagaimanapun, sejam atau lebih masih cukup baik dan cukup pantas untuk keperluan saya.

  • Hasil kualiti yang baik. Bleve berfungsi dengan baik dengan hasil kata kunci tetapi beberapa carian jenis semantik juga berfungsi dengan baik dalam Bleve.

  • Permulaan pantas: Jika anda perlu memulakan semula atau menggunakan kemas kini, hanya milisaat diperlukan untuk memulakan semula Bleve. Tiada penyekatan bacaan untuk membina semula indeks dalam ingatan, jadi carian indeks boleh dilakukan tanpa gangguan hanya beberapa milisaat selepas dimulakan semula.

Sediakan indeks?

Dalam Bleve, "Indeks" boleh dianggap sebagai jadual pangkalan data atau koleksi (NoSQL). Tidak seperti jadual SQL biasa, anda tidak perlu menentukan setiap lajur tunggal, pada asasnya anda boleh lari dengan skema lalai untuk kebanyakan kes penggunaan.

Untuk memulakan indeks Bleve, anda boleh melakukan perkara berikut:

mappings := bleve.NewIndexMapping()
index, err = bleve.NewUsing("/some/path/index.bleve", mappings, "scorch", "scorch", nil)
if err != nil {
    log.Fatal(err)
}
Salin selepas log masuk
Salin selepas log masuk

Bleve menyokong beberapa jenis indeks yang berbeza, tetapi saya dapati setelah banyak mengutak-atik bahawa jenis indeks "hangus" memberikan anda prestasi terbaik. Jika anda tidak lulus dalam 3 hujah terakhir, Bleve hanya akan lalai kepada BoltDB.

Menambah dokumen

Menambah dokumen pada Bleve adalah mudah. Anda pada asasnya boleh menyimpan sebarang jenis struct dalam indeks:

type Book struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Genre string `json:"genre"`
}

b := Book{
    ID:    1234,
    Name:  "Some creative title",
    Genre: "Young Adult",
}
idStr := fmt.Sprintf("%d", b.ID)
// index(string, interface{})
index.index(idStr, b)
Salin selepas log masuk
Salin selepas log masuk

Jika anda mengindeks sejumlah besar dokumen, lebih baik menggunakan kumpulan:

// You would also want to check if the batch exists already
// - so that you don't recreate it.
batch := index.NewBatch()
if batch.Size() >= 1000 {
    err := index.Batch(batch)
    if err != nil {
        // failed, try again or log etc...
    }
    batch = index.NewBatch()
} else {
    batch.index(idStr, b)
}
Salin selepas log masuk
Salin selepas log masuk

Seperti yang anda akan perhatikan, tugas yang rumit seperti menyusun rekod dan menulisnya pada indeks dipermudahkan menggunakan "index.NewBatch" yang mencipta bekas untuk mengindeks dokumen buat sementara waktu.

Selepas itu anda hanya semak saiz semasa anda melingkari dan siram indeks sebaik sahaja anda mencapai had saiz kelompok.

Mencari indeks

Bleve mendedahkan berbilang penghurai pertanyaan carian berbeza yang boleh anda pilih bergantung pada keperluan carian anda. Untuk memastikan artikel ini pendek dan manis, saya hanya akan menggunakan penghurai rentetan pertanyaan standard.

searchParser := bleve.NewQueryStringQuery("chicken reciepe books")
maxPerPage := 50
ofsset := 0
searchRequest := bleve.NewSearchRequestOptions(searchParser, maxPerPage, offset, false)
// By default bleve returns just the ID, here we specify
// - all the other fields we would like to return.
searchRequest.Fields = []string{"id", "name", "genre"}
searchResults, err := index.Search(searchResult)
Salin selepas log masuk
Salin selepas log masuk

Dengan hanya beberapa baris ini, anda kini mempunyai enjin carian yang berkuasa yang memberikan hasil yang baik dengan memori dan jejak sumber yang rendah.

Berikut ialah perwakilan JSON bagi hasil carian, "hits" akan mengandungi dokumen yang sepadan:

{
    "status": {
        "total": 5,
        "failed": 0,
        "successful": 5
    },
    "request": {},
    "hits": [],
    "total_hits": 19749,
    "max_score": 2.221337297308545,
    "took": 99039137,
    "facets": null
}
Salin selepas log masuk
Salin selepas log masuk

Faceting

Seperti yang dinyatakan sebelum ini, Bleve menyediakan sokongan aspek penuh di luar kotak tanpa perlu menyediakannya dalam skema anda. Untuk Facet pada buku "Genre" misalnya, anda boleh melakukan perkara berikut:

//... build searchRequest -- see previous section.
// Add facets
genreFacet := bleve.NewFacetRequest("genre", 50)
searchRequest.AddFacet("genre", genreFacet)
searchResults, err := index.Search(searchResult)
Salin selepas log masuk
Salin selepas log masuk

Kami melanjutkan Permintaan carian kami dari awal dengan hanya 2 baris kod. "NewFacetRequest" mengambil 2 argumen:

  • Medan: medan dalam indeks kami kepada faset pada (rentetan).

  • Saiz: bilangan entri untuk dikira (integer). Oleh itu dalam contoh kami, ia hanya akan mengira 50 genre pertama.

Yang di atas kini akan mengisi "faset" dalam hasil carian kami.

Seterusnya, kami hanya menambah aspek kami pada permintaan carian. Yang mengambil "nama aspek" dan aspek sebenar. "Nama facet" ialah "kunci" anda akan dapati keputusan ini ditetapkan di bawah dalam hasil carian kami.

Pertanyaan lanjutan dan penapisan

Walaupun penghurai "QueryStringQuery" boleh memberikan anda sedikit perbatuan; kadangkala anda memerlukan pertanyaan yang lebih kompleks seperti "seseorang mesti sepadan" di mana anda ingin memadankan istilah carian dengan beberapa medan dan mengembalikan hasil asalkan sekurang-kurangnya satu medan sepadan.

Anda boleh menggunakan jenis pertanyaan "Disjunction" dan "Conjunction" untuk mencapai ini.

  • Pertanyaan Konjungsi: Pada asasnya, ia membolehkan anda merangkaikan berbilang pertanyaan bersama-sama untuk membentuk satu pertanyaan gergasi. Semua pertanyaan kanak-kanak mesti sepadan dengan sekurang-kurangnya satu dokumen.

  • Pertanyaan Disjungsi: Ini akan membolehkan anda melakukan pertanyaan "satu mesti sepadan" yang dinyatakan di atas. Anda boleh menghantar dalam x jumlah pertanyaan dan menetapkan bilangan pertanyaan kanak-kanak mesti sepadan dengan sekurang-kurangnya satu dokumen.

Contoh Pertanyaan Disjungsi:

mappings := bleve.NewIndexMapping()
index, err = bleve.NewUsing("/some/path/index.bleve", mappings, "scorch", "scorch", nil)
if err != nil {
    log.Fatal(err)
}
Salin selepas log masuk
Salin selepas log masuk

Sama seperti cara kami menggunakan "searchParser" sebelum ini, kami kini boleh menghantar "Disjunction Query" ke dalam pembina untuk "searchRequest" kami.

Walaupun tidak betul-betul sama, ini menyerupai SQL berikut:

type Book struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Genre string `json:"genre"`
}

b := Book{
    ID:    1234,
    Name:  "Some creative title",
    Genre: "Young Adult",
}
idStr := fmt.Sprintf("%d", b.ID)
// index(string, interface{})
index.index(idStr, b)
Salin selepas log masuk
Salin selepas log masuk

Anda juga boleh melaraskan betapa kaburnya carian anda mahukan dengan menetapkan "query.Fuzziness=[0 atau 1 atau 2]"

Contoh Pertanyaan Kata Hubung:

// You would also want to check if the batch exists already
// - so that you don't recreate it.
batch := index.NewBatch()
if batch.Size() >= 1000 {
    err := index.Batch(batch)
    if err != nil {
        // failed, try again or log etc...
    }
    batch = index.NewBatch()
} else {
    batch.index(idStr, b)
}
Salin selepas log masuk
Salin selepas log masuk

Anda akan perasan sintaksnya sangat serupa, pada asasnya anda hanya boleh menggunakan pertanyaan "Konjungsi" dan "Disjungsi" secara bergantian.

Ini akan kelihatan serupa dengan yang berikut dalam SQL:

searchParser := bleve.NewQueryStringQuery("chicken reciepe books")
maxPerPage := 50
ofsset := 0
searchRequest := bleve.NewSearchRequestOptions(searchParser, maxPerPage, offset, false)
// By default bleve returns just the ID, here we specify
// - all the other fields we would like to return.
searchRequest.Fields = []string{"id", "name", "genre"}
searchResults, err := index.Search(searchResult)
Salin selepas log masuk
Salin selepas log masuk

Ringkasnya; gunakan "Pertanyaan Konjungsi" apabila anda mahu semua pertanyaan kanak-kanak sepadan dengan sekurang-kurangnya satu dokumen dan "Pertanyaan Disjungsi" apabila anda mahu memadankan sekurang-kurangnya satu pertanyaan kanak-kanak tetapi tidak semestinya semua pertanyaan kanak-kanak.

Sharding

Jika anda menghadapi masalah kelajuan, Bleve juga memungkinkan untuk mengedarkan data anda merentasi berbilang serpihan indeks dan kemudian menanyakan serpihan tersebut dalam satu permintaan, contohnya:

{
    "status": {
        "total": 5,
        "failed": 0,
        "successful": 5
    },
    "request": {},
    "hits": [],
    "total_hits": 19749,
    "max_score": 2.221337297308545,
    "took": 99039137,
    "facets": null
}
Salin selepas log masuk
Salin selepas log masuk

Sharding boleh menjadi agak rumit, tetapi seperti yang anda lihat di atas, Bleve menghilangkan banyak kesakitan, kerana ia secara automatik "menggabungkan" semua indeks dan carian merentasinya, dan kemudian mengembalikan hasil dalam satu set keputusan sama seperti anda mencari satu indeks.

Saya telah menggunakan sharding untuk mencari 100 shards. Keseluruhan proses carian selesai dalam purata 100-200 milisaat sahaja.

Anda boleh mencipta serpihan seperti berikut:

//... build searchRequest -- see previous section.
// Add facets
genreFacet := bleve.NewFacetRequest("genre", 50)
searchRequest.AddFacet("genre", genreFacet)
searchResults, err := index.Search(searchResult)
Salin selepas log masuk
Salin selepas log masuk

Cuma pastikan anda membuat ID unik untuk setiap dokumen atau mempunyai semacam cara yang boleh diramal untuk menambah dan mengemas kini dokumen tanpa mengacaukan indeks.

Cara mudah untuk melakukan ini ialah dengan menyimpan awalan yang mengandungi nama serpihan dalam DB sumber anda, atau dari mana-mana sahaja anda mendapatkan dokumen itu. Supaya setiap kali anda cuba memasukkan atau mengemas kini, anda mencari "awalan" yang akan memberitahu anda bahagian mana yang hendak dipanggil ".index".

Bercakap tentang pengemaskinian, hanya memanggil "index.index(idstr, struct)" akan mengemas kini dokumen sedia ada.

Kesimpulan

Hanya menggunakan teknik carian asas ini di atas dan meletakkannya di belakang GIN atau pelayan HTTP Go standard, anda boleh membina API carian yang cukup berkuasa dan menyediakan berjuta-juta permintaan tanpa perlu melancarkan infrastruktur yang kompleks.

Satu kaveat; Bleve tidak memenuhi keperluan replikasi, bagaimanapun, kerana anda boleh membungkusnya dalam API. Hanya ada tugas cron yang membaca daripada sumber anda dan "meletupkan" kemas kini kepada semua pelayan Bleve anda menggunakan goroutine.

Sebagai alternatif, anda hanya boleh mengunci tulisan ke cakera selama beberapa saat dan kemudian hanya "rsync" data merentas ke indeks hamba, walaupun saya tidak menasihati berbuat demikian kerana anda mungkin juga perlu memulakan semula binari pergi setiap kali .

Atas ialah kandungan terperinci Bleve: Bagaimana untuk membina enjin carian pantas roket?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel terbaru oleh pengarang
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan