Xây dựng dịch vụ tìm kiếm với Go và Elaticsearch

Huy Ngô
8 min readApr 21, 2020

--

Hello xin chào các bạn, là mình nha :))) Xin lỗi các bạn vì mình đã vắng bóng 1 thời gian. Lần quay trở lại này chúng ta sẽ cùng tiếp tục tìm hiểu thêm về một Service tìm kiếm được mang tên là Elasticsearch.

YO bắt đầu thôi nào.

I. Elasticsearch là gì.!?

Elasticsearch là một công cụ tìm kiếm dựa trên nền tảng Apache Lucene. Nó cung cấp một bộ máy tìm kiếm dạng phân tán, có đầy đủ công cụ với một giao diện web HTTP có hỗ trợ dữ liệu JSON. Elasticsearch được phát triển bằng Java và được phát hành dạng nguồn mở theo giấy phép Apache.

Lý thuyết thế thôi, có rất nhiều bài đã giới thiệu cũng như nói rõ hơn về Elasticsearch rồi. Mình sẽ tóm tắt nó lại các ý chính như sau:

  • Elasticsearch là một search engine.
  • Elasticsearch được kế thừa từ Lucene Apache
  • Elasticsearch thực chất hoặt động như 1 web server, có khả năng tìm kiếm nhanh chóng (near realtime) thông qua giao thức RESTful
  • Elasticsearch có khả năng phân tích và thống kê dữ liệu
  • Elasticsearch chạy trên server riêng và đồng thời giao tiếp thông qua RESTful do vậy nên nó không phụ thuộc vào client viết bằng gì hay hệ thống hiện tại của bạn viết bằng gì. Nên việc tích hợp nó vào hệ thống bạn là dễ dàng, bạn chỉ cần gửi request http lên là nó trả về kết quả.
  • Elasticsearch là 1 hệ thống phân tán và có khả năng mở rộng tuyệt vời (horizontal scalability). Lắp thêm node cho nó là nó tự động auto mở rộng cho bạn.
  • Elasticsearch là 1 open source được phát triển bằng Java

Những khái niệm cần biết:

1. Document: Là một JSON object với một số dữ liệu. Đây là basic information unit trong ES. Hiểu 1 cách cơ bản thì đây là đơn vị nhỏ nhất để lưu trữ dữ liệu trong Elasticsearch.

2. Index: Index có lẽ là 1 khái niệm quá quen thuộc đối với các anh em dùng Mysql rồi. Khi đọc đến đây có lẽ ae đã thừa hiểu chức năng của index là gì rồi. Tuy nhiên nếu các bạn nghĩ rằng index trong ES hoàn toàn giống trong Mysql thì các bạn nhầm rồi nhé ! Trong Elasticsearch , sử dụng một cấu trúc được gọi là inverted index . Nó được thiết kế để cho phép tìm kiếm full-text search. Cách thức của nó khá đơn giản, các văn bản được phân tách ra thành từng từ có nghĩa sau đó sẽ đk map xem thuộc văn bản nào. Khi search tùy thuộc vào loại search sẽ đưa ra kết quả cụ thể.

3. Shard: Là đối tượng của Lucene , là tập con các documents của 1 Index. Một Index có thể được chia thành nhiều shard. Mỗi node bao gồm nhiều Shard . Chính vì thế Shard mà là đối tượng nhỏ nhất, hoạt động ở mức thấp nhất, đóng vai trò lưu trữ dữ liệu.

Chúng ta gần như không bao giờ làm việc trực tiếp với các ShardElasticsearch đã support toàn bộ việc giao tiếp cũng như tự động thay đổi các Shard khi cần thiết.

Có 2 loại Shard là : primary shardreplica shard.

  • Primary shard: Sẽ lưu trữ dữ liệu và đánh index . Sau khi đánh xong dữ liệu sẽ được vận chuyển tới các Replica Shard.
    Mặc định của Elasticsearch là mỗi index sẽ có 5 Primary shard và với mỗi Primary shard thì sẽ đi kèm với 1 Replica Shard.
  • Replica shard: Replica Shardđúng như cái tên của nó, nó là nơi lưu trữ dữ liệu nhân bản của Primary shard.
    Replica Shard
    có vai trò đảm bảo tính toàn vẹn của dữ liệu khi Primary Shard xảy ra vấn đề.
    Ngoài ra Replica Shard có thể giúp tăng cường tốc độ tìm kiếm vì chúng ta có thể setup lượng Replica Shard nhiều hơn mặc định của Elasticsearch.

4. Node: Là trung tâm hoạt động của Elasticsearch. Là nơi lưu trữ dữ liễu ,tham gia thực hiện đánh index của cluster cũng như thực hiện các thao tác tìm kiếm. Mỗi node được định danh bằng 1 unique name.

5. Cluster: Tập hợp các nodes hoạt động cùng với nhau, chia sẽ cùng thuộc tính cluster.name. Chính vì thế Cluster sẽ được xác định bằng 1 'unique name'. Việc định danh các cluster trùng tên sẽ gây nên lỗi cho các node vì vậy khi setup các bạn cần hết sức chú ý điểm này.
Chức năng chính của Cluster đó chính là quyết định xem shards nào được phân bổ cho node nào và khi nào thì di chuyển các Cluster để cân bằng lại Cluster

II. Triển khai service

Qua các bài viết trước nay chúng ta sẽ cùng áp dụng hết tất cả những gì chúng ta đã tìm hiểu nhé.

  • Docker.
  • Gin framework.
  • Golang (Tất nhiên rồi , chúng ta đang trong seri "Golang bí kiếp học" cơ mà.

Lẹt đu đ...à Let's do it:))))

1. Config service

Tạo file docker-compose.yaml với nội dung như sau:

docker-compose.yaml

Với file config này thì chúng ta sẽ thiết lập được cho 2 service.

  • api: Cho phép Go chạy trên local host với port là 8080
  • elasticsearch: Sẽ chạy phiên bản chính thức của elascticsearch của Docker.

Tạo thư mục app và khởi tạo project với dep.

$ mkdir search-api
$ cd search-api
$ dep init

Tạo file dockerfilecho api service trong thư mụcapp.

dockerfile

2. Kết nối tới elasticsearch

Tạo filemain.go trong thư mụcapp.

main.go

Khai báo index và type của Elaticsearch

Chúng ta có dữ liệu từ Postgres như sau.

Vì vậy chúng ta sẽ khai báo cấu trúc cho nó như sau.

User struct

Tại func main() chúng ta sẽ tạo client của elasticsearch

Có một sự khác biệt về thời gian giữa một Docker container khởi động và dịch vụ bên trong nó sẵn sàng để kết nối. Vì lý do đó, đoạn mã trên cố gắng kết nối lại với dịch vụ elaticsearch cứ sau 3 giây, nếu nó bị lỗi.

Sau đó chúng ta cũng sẽ khai báo và tạo func kết nối tới Postgres

Tạo một func hỗ trợ trả về kết quả bị lỗi.

Tạo func CreateCustomer để xử lý việc tạo index.

func CreateCustomer()

Tiếp đến là việc lấy dữ liệu từ Postgres lên.

Khi đã lấy được dữ liệu từ Postgres lên chúng ta sẽ thêm nó vào index của elasticsearch bằng hàm Index() được cung cấp sẵn trong gói package của olivere/elastic .

Mình lại sử dụng lại c.JSON ở đây để kiểm tra status của request trả về, nếu thành công sẽ xuất ra màn hình thông tin của user.
Chúng ta chạy thử nhé. Các bạn nhớ phải run docker chứa image của elasticsearch mà chúng ta đã config nhé.

$ docker-compose up -d --build

Và dữ liệu đã được lấy lên từ Postgres và cũng đã được thêm vào index rồi.
Chúng ta thử chạy trên port của elasticsearch thử nhé.

Và chúng ta đã thấy được dữ liệu đã được thêm vào index customer, type user mà chúng ta đã khai báo. Và ở lệnh trên mình sử dụng "?pretty" để trả kết quả về là Json để chúng ta dễ đọc hơn.

Vậy là chúng ta đã hoàn thành được func tạo index cũng như là lấy dữ liệu từ Postgres insert vào index rồi. Cùng hoàn thiện 2 client search và update còn lại nhé.

Client search

func GetUserByID

Chạy thử trên Postman, chúng ta sẽ truyền vào key là id, và value là id của user chúng ta muốn search.

Client update

func update

Tại đây mình cũng sẽ cập nhật dữ liệu trên index cũng như mình sẽ cập nhật dữ liệu này xuống Postgres luôn để giữ được tính đồng nhất dữ liệu nhé.

Kiểm tra dữ liệu ở Postgres nào.

Vậy là chúng ta đã update được lại ví tiền của user rồi, lần này hơi đắng cho "Đệ Nhất Quốc Sư Hoa Kỳ Nhà Tiên Tri Vũ Trụ Trần Dần" của chúng ta chắc lấy tiền bao gái :))) nên trong người chỉ còn lại 699k trong người :)))

Đến đây thì thôi chắc mình cũng như "Đệ Nhất Quốc Sư Hoa Kỳ Nhà Tiên Tri Vũ Trụ Trần Dần" phải kiếm lại ví tiền của mình thôi :))) Qua bài viết này hi vọng các bạn đã hiểu hơn về ElasticSearch cũng như có thể tạo cho mình một service riêng. Hi vọng sẽ sớm gặp lại các bạn trong phần tiếp theo của Seri "Go lang bí kiếp học" nhé.
Cảm ơn các bạn đã bỏ chút ít thời gian xem bài viết của mình, mình sẽ để lại source đã demo cùng các bạn nãy giờ tại Git Hub nhé.!!
Xin chào các bạn và hẹn gặp lại Bái bai......Yeah yeah yeah yeah yeah :)))

--

--