Monitor việc gọi API real-time với Go và Redis

Hôm trước, tui có một bài viết về việc sử dụng cơ chế Pub/Sub của Redis bằng command line. Hôm nay tui sẽ dùng Golang kết hợp với Redis để làm một cái demo nho nhỏ để đẩy message qua lại giữa Weather Services với API Monitor Server, sau đó dùng socket để broadcast message này ra web.

1. Mô tả bài toán:

Khỏi nói nhiều, các bạn xem hình sẽ hiểu:

Dùng Postman để gọi endpoint API (như trong bài Concurrency trong Golang) “http://localhost:9000/api/temperature/city_name”. Thì chương trình “Weather Services” sẽ xử lý việc tính nhiệt độ, sau đó trả dữ liệu về cho user đồng thời sẽ PUBLISH một message dạng json vào Redis thông qua một channel.

Tiếp, một chương trình khác là “API Monitor” sẽ SUBSCRIBE channel này để nhận message json, sau đó sẽ dùng socket để broadcast message này đến các web client đang kết nối.

2. Viết code chương trình:

Weather Services:

Đối với chương trình này, chúng ta sẽ sử dụng lại code của bài trước và thêm phần connect với Redis:

Install Redis client:

$ go get -u “github.com/gomodule/redigo/redis”

tại file main.go:

import package: “github.com/gomodule/redigo/redis”

 

Tạo kiểu dữ liệu mới:

Thêm một hàm để publish message qua Redis:

Hàm publishMessage() sẽ nhận 2 tham số:

  • Redis Connection
  • Channel lưu giữ data cần publish

Ta sẽ cho hàm này chạy như là một routine.

Trong hàm main, ta thêm đoạn code để connect với Redis:

Trong hàm xử lý của api endpoint “/api/temperature/{city}”, thêm vào cuối hàm này đoạn code sau:

Sửa hàm temperature để trả về 2 giá trị, 1 là nhiệt độ, 2 là success:

Thêm biến success khi gọi hàm temperature():

API Monitor:

Sử dụng iris để tạo Web Server:

install iris:

$ go get -u “github.com/kataras/iris”

install go-socket.io:

$ go get -u “github.com/googollee/go-socket.io”

file main.go:

import package:

Tạo hàm deliverMessage() để nhận message từ Redis và broadcast thông qua socket

Hàm này nhận 2 tham số:

  • Redis Connection
  • Socket Server

Tiếp, trong hàm main();

Tạo Web server và socket:

Connect đến Redis và tạo Pub/Sub connection để Subscribe message:

Mở socket:

Tạo routine:

Run Iris web server:

tạo một file index.html trong thư mục public, để làm web client (sử dụng jquery, socket.io client):

3. Chạy chương trình:

Run Redis server (Xem chi tiết các chạy Redis ở bài Redis Pub/Sub):

redisserver

Tại thư mục weather-services-redis:

$ go run main.go

Tại thư mục api-monitor:

$go run main.go

Mở Chrome và Firefox, nhập url http://localhost:8000

Dùng Postman để gọi API:

http://localhost:9000/api/temperature/tokyo

http://localhost:9000/api/temperature/seoul

http://localhost:9000/api/temperature/saigon

Khi đó, các bạn sẽ thấy kết quả json vừa trả về cho Postman và đồng thời Message cũng sẽ được notify trên Chrome và Firefox.

Kết quả như sau:

 

Source code:

https://github.com/lmdat/weather-services-redis

https://github.com/lmdat/api-monitor

 

Read More

Concurrency kết hợp với RESTful API trong Golang

Sau vài ngày “chìm đắm” trong mớ hỗn độn của ngôn ngữ Go để tìm hiểu về cái gọi là concurrency với 2 khái niệm mới đó là GoRoutine và Channel thì tui thấy rằng bạn Go này hay quá. Chưa bao giờ thấy việc lập trình concurreny nó dễ dàng như vậy. Sau khi “học” xong thì cảm thấy Go nó có một chút gì đó mạnh mẽ của C/C++, uyển chuyển của PHP/Python. Cú pháp thì gọn gàng (có thời gian, tui sẽ viết bài về cách học ngôn ngữ Go này).

Đó giờ tui làm backend chỉ dùng PHP là chính bao gồm viết website cũng như làm RESTful api. Cái hạn chế của PHP đó là PHP là scripting, có timeout khi run script. Cho nên khi build API hoặc là gọi API từ một service khác sẽ gặp một số rủi ro về việc blocking request trong trường hợp tạo nhiều request để gọi API cùng lúc. Có thể tại thời điểm này, PHP cũng đã có những giải pháp để giải quyết vấn đề trên. Nhưng hôm nay, tui muốn sử dụng Go để làm giải pháp cho vấn đề non-blocking request.

1. Goroutine và channel

Bài toán đặt ra là như thế này. Tui có một hệ thống cung cấp Rest API, khi gọi đến endpoint này, hệ thống sẽ phải collect dữ liệu từ nhiều nguồn khác từ các service khác thông qua Rest API luôn, sau đó tổng hợp dữ liệu này lại và trả kết quả về cho người dùng. Giả sử tui phải gọi API từ 15 service khác nhau, nếu bình thường, tui sẽ phải tạo 15 cái request và thực hiện tuần tự 15 cái request này. Nếu trung bình, 1 request xử lý xong trong vòng từ 0.5s – 1s thì 15 request tui mất khoản 7.5s – 15s (do vấn đề blocking request, xử lý xong request này mới đến request kế tiếp), điều này thật sự không thể chấp nhận đối với việc gọi API.

Nếu ta áp dụng Concurrency (nó hơi khác với Parallel nha) với 15 request này, có nghĩa là cho 15 request này được xử lý đồng thời thì tui chỉ mất từ 0.5s – 1s cho tất cả các request, vậy thì performance tăng lên rất nhiều.

Ví dụ cụ thể đi ha. Tui đã đăng ký 3 cái api để lấy nhiệt độ tại thời điểm request của một thành phố bất kỳ đó là:

(Chỉ tìm được 3 cái này nó free thôi, coi như là nhiều hơn 1 là được rồi, để minh họa bài toán trên cũng ok nha.)

Tạo 3 routine để xử lý đồng thời 3 cái request đến 3 cái api trên, dùng channel để hứng dữ liệu sau khi các routine này xử lý phần lấy giá trị nhiệt độ từ kết qua json trả về. Tiếp theo, tính trung bình nhiệt độ có được và trả kết quả trung bình này cho Rest API, sau đó Rest API này sẽ trả kết quả json về cho người dùng. Xem cái hình sau nhé:

2. Code chương trình bằng Go:

2.1. Tạo Interface:

Do ta có 3 service cần gọi, mỗi service này thì trả dữ liệu JSON về có cấu trúc khác nhau, nên ta cần tạo một interface chung có một hàm là GetTemperature, sau đó tạo 3 cái provider tương ứng với 3 service rồi implement cái interface này.

weatherapi/weather_interface.go

2.1. Tạo các provider:

Mỗi provider cụ thể sẽ có  fields: APIKey và URL:

weatherapi/open_weather_map.go

weatherapi/api_xu.go

weatherapi/weather_bit.go

2.3. Tạo hàm temperature() để collect và tính nhiệt độ trung bình:

Tạo một danh sách chứa các Provider:

Tạo kiểu dữ liệu TemperatureData để lưu dữ liệu là nhiệt độ (độ C, độ K, độ F) để trả về cho người dùng khi người dùng gọi Rest API

Hàm temperature, trong hàm này ta sẽ dùng goroutine và channel

2.4. Khởi tạo các provider:

Tạo các provider với APIKey và URL tương ứng, sau đó thêm chúng vào ProviderList:

2.5. Tạo Rest API với Gorilla Mux:

Tổng hợp lại trong file main.go:

3. Run chương trình:

Đứng ngay tại thư mục chứa file main.go, run lệnh:

$ go run main.go

Dùng PostMan để test Rest API:

http://localhost:9000/api/temperature/saigon

http://localhost:9000/api/temperature/tokyo

http://localhost:9000/api/temperature/paris

Kết quả hiển thị bên server:

Tui để source code tham khảo bài này tại git nhé: https://github.com/lmdat/weather-services

Cách viết của tui trong bài này thật sự chưa hoàn hảo đâu, sẽ có một vài bug xuất hiện đó, nhưng về cơ bản, hi vọng bài viết này sẽ giúp các bạn có hứng thú với ngôn ngữ Go hơn cũng như cách sử dụng goroutinechannel trong lập trình concurrency.

Read More