[Elasticsearch] integrate elasticsearch with laravel (5.2)

Chào mọi người hôm nay mình sẽ cùng đi vào tìm hiểu chút về một công nghệ tìm kiếm thời gian thực đó là Elasticsearch.

  • Các bạn cũng như mình có thể cũng đã nghe nhiều về từ khóa "Elasticsearch", trước đó mình cũng có nghe qua nó từ bạn bè và có thể một số bài chia sẻ blog nào đó, cũng biết đây là một công nghệ tìm kiếm với tốc độ trả về kết quả là rất nhanh và mình chỉ biết có thể. Nên hôm nay mình muốn tìm hiểu sâu chút và muốn thử demo một ứng dụng sử dụng elasticsearch.

  • Các bạn có thể thấy giờ chúng ta sử dụng bất cứ một ứng dụng hay website nào thì họ đề cung cấp cho mình một chức năng cơ bản đó là tìm kiếm (searching). Giả sử mình đang sử dụng ứng dụng nghe nhạc ngoài việc tìm kiếm theo tên bài hát, tên ca sĩ hay tên tác giả sáng tác nhưng nếu bây giờ mình chỉ nhớ có một hai câu ê a trong nội dung bài hát thì mình sẽ làm sao tìm nó trong một kho dữ liệu đồ sộ giờ? Mình nhận thấy là Elasticsearch hỗ trợ rất tốt cho vấn đề vừa đặt ra của chúng ta đó là tìm kiếm trong nội dung, và có thể chúng ta không nhớ chính xác câu hát đó nhưng khi tìm kiếm gần đúng hay kể cả viết tiếng anh có chút lỗi về chính tả thì kết quả của mình vẫn có thể được trả về như mong muốn.

  • Một số từ khóa mình cần tìm hiểu trước khi đi vào elasticsearch đó là index, inverted index (chỉ mục ngược), tf.idf (thuật toàn tính điểm cho một tài liệu được trả về với mỗi truy vấn, tf-term frequent: tần suất xuất hiện của từ,* df-document frequent*: số văn bản chứa từ, idf: trọng số của tần suất văn bản chứa từ)

Một số điều cần biết về elasticsearch

Elasticsearch là gì?

  • Search engine mạnh mẽ, kế thừa và phát triển từ Lucene Apache ( Search engine là một chương trình tìm kiếm dữ liệu thông qua từ khóa và trả về một danh sách kết quả liên quan tới từ khóa đó, ví dụ như Google, Yahoo, CocCoc ...)
  • Có khả năng mở rộng dữ liệu thiêu chiều ngang, tốc độ tìm kiếm rất nhanh ("gần như real time")
  • Hoạt động như là một server phân tán và giao tiếp với cơ chế Resful
  • Dữ liệu cấu trúc dạng Json

Kiến trúc, quá trình xử lý dữ liệu

  • Một tài liệu với độ tin cậy dữ liệu gần như tuyệt đối đó chính là trang chủ của elasticsearch là elastic.co
  • Mọi người có thể tham khảo một bài viết Elasticsearch của tác giả Đinh Hoàng Long mình thấy anh ấy viết khá chi tiết và dễ đọc hiểu đối với người mới như chúng ta
  • Phân tích dữ liệu hình 1 Chúng ta có thể thấy rõ các bước xử lý dữ liệu đầu vào trong elasticsearch như sau
    • Loại bỏ thẻ tag
    • Tách từ
    • Loại bỏ stop word (những từ thường xuyên xuất hiện nhưng không ảnh hưởng nhiều đến kết quả tìm kiếm như and, then, the, this, that ... những từ này sẽ có idf nhỏ
    • Cuối cùng là đánh index cho từng từ trong nội dung dữ liệu của chúng ta

Đánh giá

Ưu điểm

  • Elasticsearch có tốc độ tìm kiếm tốt
  • Có nhiều tài liệu hỗ trợ chi tiết
  • Dễ dàng trong việc scaling mở rộng dữ liệu
  • Hỗ trợ tìm kiếm "fuzzy" search cho việc mình nhập vào từ có lỗi chính tả hay nhập thiếu một phần từ khóa nhưng vẫn có thể trả ra kết quả mong muốn
  • Hỗ trợ cả cơ chế tính điểm đối với kết quả trả về dựa trên tf.idf (term frequent.inverse document frequent)
  • Hỗ trợ nhiều loại truy vấn như match query, full text search, fuzzy search
  • Phù hợp làm second data store cho việc lưu trữ những dữ liệu cần được đánh indexed và tìm kiếm dạng full-text search

Không phải ưu điểm

  • Không hỗ trợ bất kỳ cơ chế xác thực hay quyền truy cập nào
  • Không hỗ trợ transaction, là việc khi chúng ta thực hiện thay đổi nhiều bản ghi nếu xảu ra lỗi thì sẽ làm cho logic của mình bị sai hay dẫn tới mất mát dữ liệu
  • Phức tạp hơn cho việc tích hợp với ứng dụng và chưa được nhiều framework hỗ trợ về truy vấn
  • Không phù hợp với hệ thống có dữ liệu thường xuyên thay đổi, vì thường xuyên thay đổi dẫn tới chi phí bỏ ra đễ đánh chỉ mục là nhiều Data Availability khiến dữ liệu near realtime chứ không hẳn realtime nếu như mà có yêu cầu cập nhật dữ liệu ngay. (Ví dụ như khi mà có nhiều comment vào các post cùng một lúc thì khi reload lại trang có thể sẽ không trả về được bài post đó ngay vì dữ liệu có thể đang được đánh lại index)
  • Tránh việc sử dụng elasticsearch như là primary data store Durability: tính bền vững dữ liệu. Elasticsearch phân tán dữ liệu và khá là ổn định tuy nhiên tính bền vững không có độ ưu tiên cao như là các hệ cơ sở dữ liệu khác. Điều này quan trọng khi mà mình chọn elasticsearch là primary data store khi mà dữ liệu bị mất thì khá nguy hiểm
  • Elasticsearch không phải hệ thống ACID (Atomicity, Consistency, Isolation, và Durability)
  • Mất mát dữ liệu, do cơ chế single node partitions trong elasticsearch

Demo sử dụng Elasticsearch với ứng dụng Laravel

Cài đặt

  • Mình đang sử dụng phiên bản laravel 5.2 và PHP 7.

Cài đặt elasticsearch

  • Cài đặt phiên bản mới nhất phù hợp với hệ điều hành theo guide hướng dẫn trên trang chủ của elasticsearch
  • Hệ điều hành mình đang dùng là ubuntu 16.04 nên mình down bản .deb về và tiến hành cài như ứng dụng bình thường
  • Sau khi cài đặt xong cần start service Elastic và test xem liệu nó đã hoạt động bằng cách sử dụng commands như sau

Có thể thấy là mình đã cài đặt thành công với phiên bản Elasticsearch 5.1, nhưng do máy của mình yếu nên việc start và sử dụng service khá chậm chạp mà mình lại thích sử dụng docker nên mình dùng docker cho việc setup elastic service chạy nhẹ mà đảm bảo tốc độ nhanh =)) Nếu bạn chưa biết docker là gì thì có thể tham khảo 2 bài viết trước của mình về docker Docker với lập trình viên web Docker compose: Xây dựng môi trường phát triển ứng dụng web

  • Cài đặt Elasticsearch với docker

    • Download image elasticsearch bản 5.0 trên docker hub tại link sau elasticsearch
    • Do mình đã cài đặt elasticsearch trên máy của mình nên có thể sẽ có trường hợp xung đột cổng 9200 mà elasticsearch sử dụng do đó để đảm bảo việc xung đột không xảy ra thì mình sẽ start elasticsearch image và export ra cổng 9300 để thực hiện test bên ngoài host
  • Môi trường start ứng dụng của mình cũng sẽ chạy toàn bộ bên trong docker

  • Sử dụng docker-compose để khai báo các image cần sử dụng

     version: "2"
    services:
    
    selfproject:
        build: .
        #image: tutum/apache-php
        links:
            - elasticsearch
            - mysql:mysql-server
        ports:
            - "8888:80"
        networks:
            - back-tier
        volumes:
            - .:/app
        environment:
            - ALLOW_OVERRIDE=true
        hostname: selfproject
        #domainname: coclab.lan
        cpu_shares: 512             # 0.5 CPU
        mem_limit: 536870912        # 512 MB RAM
        # privileged: true
        # restart: on-failure:3     # other choice: always
        # stdin_open: true
        # tty: true
    
    mysql:
        image: mysql
        ports:
            - "3307:3306"
        networks:
            - back-tier
        volumes:
            - ./mysql-data/:/var/lib/mysql/
        environment:
            - MYSQL_ROOT_PASSWORD=root
            - MYSQL_USER=duylx
            - MYSQL_PASSWORD=1234
            - MYSQL_DATABASE=selfproject
        hostname: mysql
        #domainname: coclab.lan
        cpu_shares: 512             # 0.5 CPU
        mem_limit: 536870912        # 512 MB RAM
        # privileged: true
        # restart: on-failure:3     # other choice: always
        # stdin_open: true
        # tty: true
    
    elasticsearch:
        image: elasticsearch
        ports:
            - "9300:9200"
        networks:
            - back-tier
        volumes:
            - ./els-data/:/usr/share/elasticsearch/data
        hostname: elasticsearch
        cpu_shares: 512             # 0.5 CPU
        mem_limit: 536870912        # 512 MB RAM
    
    networks:
        back-tier:
    
  • Mình sử dụng thư viện Elasticquent - Elasticsearch for Eloquent Laravel Models cho việc dễ dàng mapping giữa elasticsearch và eloquent. Mình có thể sử dụng cú pháp với một số hàm hỗ trợ với Eloquent một cách dễ dàng

    • Cài đặt theo hướng dẫn và tạo file elasticquent.php Trong terminal đi tới đường dẫn chứa thư mục project chạy lệnh

        php artisan vendor:publish --provider="Elasticquent\ElasticquentServiceProvider"
      

    File config elasticquent.php của mình

Ở đây default_index giống như là tên database ứng dụng của mình trong Mysql hostelasticsearch do trong file docker-composer.yml mình đã config tên của hosts. Nếu như bạn chạy service elasticsearch trực tiếp trên local thì ở đó sẽ config là localhost:9200 chính là link tới service elasticsearch của mình. Trong config/app.php mình cần thêm một chút config để sử dụng Elasticquent

'providers' => [
...
Elasticquent\ElasticquentServiceProvider::class,
],

'aliases' => [
...
'Es' => Elasticquent\ElasticquentElasticsearchFacade::class,
],

Mình muốn lưu trữ bảng nào để đánh index và tìm kiếm dữ liệu trên bảng đó thì sẽ sử dụng Elasticquent trong model tương ứng. Ví dụ ứng dụng của mình sẽ đánh index và tìm kiếm trên bảng items thì trong model Item.php mình sẽ làm như sau

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Elasticquent\ElasticquentTrait;

class Item extends Model
{
    use ElasticquentTrait;

    protected $table = 'items';
    protected $fillable = ['id', 'url', 'body', 'user_id'];
    protected $mappingProperties = array(
        'url' => array(
            'type' => 'string',
            'analyzer' => 'standard',
        ),
        'title' => array(
            'type' => 'string',
            'analyzer' => 'standard',
        ),
        'content' => array(
            'type' => 'text',
            'analyzer' => 'standard',
        ),
    );
}

Sử dụng class ElasticquentTrait, config các thuộc tính trong mảng mappingProperties chính là các trường mà mình muốn đánh index trong elasticsearch. Ở đây dữ liệu của mình có các trường là url, title và content, giá trị analyzer => standard tương ứng với việc tách từ cơ bản theo loại bỏ tag tách từ theo khoảng trắng, loại bỏ stop word mặc định như hình ví dụ cho phần Phân tích dữ liệu được đưa ra ở phần đầu

  • Giờ mình sẽ vào một phần khá quan trọng đó là đánh chỉ mục (indexing). Trên link github của Elasticquent có đưa ra khá nhiều phương pháp đánh index. Với mình thì mình sẽ sử dụng hàm addAllToIndex() và sử dụng hỗ trợ của php artisan tinker .

  • Database của mình là selfproject và bảng mình muốn đánh indexitems với 15 nghìn bản ghi.

  • Trước tiên mình cần start môi trường để có thể chạy ứng dụng Cho lần khởi chạy đầu tiên mình dùng

    sudo docker-compose up
    

- Kiểm tra các container đang chạy và vào trong container chứa code web của mình, đó là trong container tutum/apache và chạy php artisan tinker rồi đánh index cho bảng items như sau - Nếu bạn không sử dụng docker thì mình làm việc này đơn giản bằng cách sau Đi đến đường dẫn project

php artisan tinker
# Sau đó
App\TenModelMuonDanhIndex::addAllToIndex();
  • Khi đó kết quả mình nhận được là toàn bộ dữ liệu trong bảng items của mình đã được đánh index và giờ có thể tìm kiếm với elasticsearch

  • Giờ mình sẽ kiểm tra xem việc start service elasticsearch và đánh index đã hoạt động ok chưa? Với mình thì mình sử dụng thêm add on đối với Google chrome đó là Sense - a Json aware interface to Elasticsearch Giao diện và cách dùng như sau Server ở đây của mình là localhost:9300 như đã nói mình start service elasticsearch và export cổng 9300. server ở đây sẽ tùy thuộc vào việc config của bạn Hàm mình vừa thực hiện bắt đầu từ dòng lện số 62 Các bạn có thể đọc bài của anh Long mà mình đã đề cập ở trên link tham khảo lý thuyết và cú pháp truy vấn trên trang chủ của elastic.co là rất đầy đủ rồi nên mình sẽ không nhắc lại nữa. Vậy là môi trường và dữ liệu của mình đã sẵn sàng, vậy là bây giờ mình xử lý sang phần server.

    • Trong controller xử lý việc tìm kiếm của mình có hàm search như sau:
  • Kết quả ứng dụng tìm kiếm của mình

    • Match query đơn giản
    • Tìm kiếm chính xác: đưa cụm từ muốn tìm kiếm chính xác trong cặp double code " " Và nhiều kiểu tìm kiếm nữa mình không đưa kết quả ra ở đây.

Tổng kết

Vấn đề gặp phải

  • Thư viện Elasticquent mà mình sử dụng theo mình thấy thì hiện tại chủ của project này không còn support cho nó nữa thì phải nên các vấn đề nếu trong quá trình thực hiện bạn gặp phải thì mình có thể lên một số diễn đàn để tìm kiếm hoặc hỏi trực tiếp trên đó
  • Thực tế mình đã gặp phải vấn đề paginate (phân trang), mình giải quyết vấn đề đó bằng cách tự sửa code của thư viện khi xem một contribute đã commit đoạn code đó lên trang chủ github của Elasticquent mà chưa được merge do chủ project đã không còn support nữa.

Tài liệu tham khảo

Tóm lại

  • Vậy là mình đã có cái nhìn cơ bản về Elasticsearch và hơn thế ta có thể demo nó với ứng dụng đơn giản của riêng mình. Sau đó chúng ta có thể mở rộng ứng dụng vào project thực tế của chúng ta tùy thuộc và nhu cầu sử dụng.
  • Cảm ơn mọi người đã dành thời gian để đọc hết bài viết của mình!

All Rights Reserved