HTTP tracker phần 2: Sử dụng Redis và lưu log

Nội dung

Chào các bạn đã tới phần 2 của chuỗi bài viết lập trình HTTP tracker. Ở phần này chúng ta sẽ tập trung phần xử lý dữ liệu nhận vào từ phía client.

Ôn bài

phần trước, chúng ta đã tìm hiểu yêu cầu, xác định yêu cầu, khởi tạo project, lập trình phần nhận data trong project. Chúng ta sẽ ôn lại bài một tí về phần yêu cầu:

  • Thu thập và đo lường được số lượng đăng ký thành công/thất bại theo device (mobile, desktop), theo browsers (Google Chrome, Firefox…).
  • Thống kê theo thời gian hoàn thành đăng ký (từ lúc ấn đăng ký đến khi nhận màn hình thành công/thất bại); nếu thất bại, thống kê thêm nguyên nhân.
  • Số lượng người đăng ký là có tài khoản trên website.

Vậy để thỏa mãn yêu cầu, chúng ta sẽ tiếp tục phần xử lý dữ liệu ra được những số liệu cơ bản, phù hợp với yêu cầu.

Tổng quan

Để đáp ứng yêu cầu, chúng ta sẽ cần một bộ cơ sở dữ liệu để lưu trữ các thống kê, cũng như lưu trữ phần log đã được gửi lên, để chúng ta có thể dễ dàng đối soát, chạy lại dữ liệu, phân tích sâu hoặc sử dụng AI/ML. Ngoài ra, theo bạn đánh giá bộ cơ sở dữ liệu này cần đáp ứng lưu trữ theo giờ phù hợp nhu cầu báo và theo bạn đánh giá.

Sau khi đánh giá, Lead của bạn yêu cầu bạn sử dụng Redis để lưu trữ thống kê và log sẽ được write vào 1 file TSV(tab-separated values). Lưu ý: với các bạn có nhiều kinh nghiệm có thể sẽ thấy yêu cầu này hơi vô lý, tuy nhiên bài viết hướng tới mục tiêu đào tạo và hướng dẫn lập trình trong mảng data, nên tác giả sẽ lựa chọn bộ công nghệ phù hợp với người đọc nhầm mang tính chất hướng dẫn.

Viết code 

Chúng ta cần gắn thêm thư viện Redis vào project:

dependencies {
    ...
    compile group: 'io.vertx', name: 'vertx-redis-client', version: "${vertxVersion}"
}

Để phục vụ nhu cầu báo cáo theo các metrics theo giờ chúng ta cần sử dụng hash stored của Redis. Dữ liệu sẽ được thiết kế theo cấu trúc như sau:

Hình thiết kế keys trong Redis
Hình thiết kế keys trong Redis

Chúng ta sẽ bắt đầu thay đổi code ở bài tập trước như sau: thay đổi main method

Java
public static void main(String[] args) {
      //Each application should have one vertx instance. We can create multiple threading on one instance later.
      Vertx vertx = Vertx.vertx();
      //Create a MainStarter object from the class, so we can call the method handleRequest from the object.
      MainStarter handlerObj = new MainStarter();
      //Create a router from vertx instance.
      Router router = Router.router(vertx);
      router.post("/accept_tracking")
            .handler(BodyHandler.create())
            .handler(handlerObj::handleRequest);
      //Initialize Redis instance
      RedisOptions options = new RedisOptions()
            .setConnectionString("redis://localhost:6379");
      Future<RedisConnection> connect = Redis.createClient(vertx, options).connect();
      connect.onSuccess(res -> {
         handlerObj.setRedisAPI(RedisAPI.api(res));
         //We need to make sure Redis connection is established successfully before init the HTTP service
         HttpServer httpServer = vertx.createHttpServer();
         httpServer.requestHandler(router).listen(8080);
      });
   }

Thêm method setRedisAPI để có thể gọi từ method main:

Java
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.file.FileSystem;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.redis.client.Redis;
import io.vertx.redis.client.RedisAPI;
import io.vertx.redis.client.RedisConnection;
import io.vertx.redis.client.RedisOptions;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainStarter {

   RedisAPI redisAPI = null;

   public void setRedisAPI(RedisAPI redisAPI) {
      this.redisAPI = redisAPI;
   }
   
   DateFormat hDF = new SimpleDateFormat("yyyy-MM-dd-HH");
   DateFormat fileDF = new SimpleDateFormat("'log_'yyyy-MM-dd-HH'.tsv'");
 
   ...

Thêm vào function đẩy dữ liệu vào Redis:

Java
private void sumToRedis(Date receiveTime, JsonObject row) {
      boolean isSuccess = row.getString("result").equals("success");
      String hKey = hDF.format(receiveTime);
      if(isSuccess) {
         this.redisAPI.hincrby(hKey, "success_device:" + row.getString("device"), "1");
         this.redisAPI.hincrby(hKey, "success_browser:" + row.getString("browser"), "1");
         this.redisAPI.hincrby(hKey, "success_time", row.getInteger("duration") + "");
         if(row.getString("accountId") != null)
            this.redisAPI.hincrby(hKey, "success_reg_user", "1");
      } else {
         this.redisAPI.hincrby(hKey, "error_device:" + row.getString("device"), "1");
         this.redisAPI.hincrby(hKey, "error_browser:" + row.getString("browser"), "1");
         this.redisAPI.hincrby(hKey, "error_time", row.getInteger("duration") + "");
         this.redisAPI.hincrby(hKey, "error_cause:" + row.getString("message"), "1");
         if(row.getString("accountId") != null)
            this.redisAPI.hincrby(hKey, "error_reg_user", "1");
      }
   }

Thêm vào function chép dữ liệu vào file TSV:

Java
//Since FileSystem instance need to be created from Vertx instance, we have to put in method
   private void writeLog(Vertx vertx, Date receiveTime, JsonObject row) {
      FileSystem fs = vertx.fileSystem();
      StringBuilder sb = new StringBuilder();
      sb.append(row.getString("device")).append("\t");
      sb.append(row.getString("browser")).append("\t");
      sb.append(row.getString("result")).append("\t");
      sb.append(row.getString("message")).append("\t");
      sb.append(row.getInteger("duration")).append("\t");
      sb.append(row.getString("accountId", ""));
      fs.writeFile(fileDF.format(receiveTime), Buffer.buffer(sb.toString()));
   }

Update lại phần code trong handleRequest method

Java
   public void handleRequest(RoutingContext context) {
      //Get response data from context
      HttpServerResponse response = context.response();
      response.setStatusCode(200).end();
      try {
         //This will get body as Buffer then cast it to JsonObject
         JsonObject dataBody = context.body().asJsonObject();
         System.out.println("Data Received: " + dataBody.toString());
         //If we let above methods use their own time, it is possible that data will be discrepancy
         Date receiveTime = new Date();
         this.sumToRedis(receiveTime, dataBody);
         this.writeLog(context.vertx(), receiveTime, dataBody);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

Code đã được thay đổi xong, bây giờ chúng ta sẽ chạy thử nhé. Chúng ta sẽ lặp lại thao tác gửi giả lập dữ liệu từ Postman hay command như ở bài trước.

PowerShell
curl --location --request POST 'http://localhost:8080/accept_tracking' \
--header 'Content-Type: application/json' \
--data-raw '{
  "device": "mobile",
  "browser": "Chrome",
  "result": "success",
  "message": "OK",
  "duration": 3432,
  "accountId": null
}'

Dữ liệu được gửi thành công, bạn đã thấy log đã gửi về HTTP tracker server và có file được ghi ra trong file TSV với nội dung như bên dưới.

PowerShell
Data Received: {"device":"mobile","browser":"Chrome","result":"success","message":"OK","duration":3432,"accountId":null}
//In TSV file
mobile	Chrome	success	OK	3432	null

Để kiểm tra trên Redis dữ liệu đã được lưu trữ thành công, bạn có thể sử dụng P3X Redis UI, khi thành công dữ liệu sẽ được lưu trữ tương tự như sau:

Dữ liệu được lưu vào Redis từ HTTP
Dữ liệu được lưu vào Redis

Tóm tắt

Ở phần này chúng ta đã biết thêm về:

  • Cách sử dụng Redis trên Vertx để lưu trữ key dữ liệu.
  • Cách sao lưu dữ liệu vào file dưới dạng TSV bằng Vertx.

Các bạn có thể trực tiếp lấy git source của bài viết ở đây Github. Ở phần tiếp theo chúng ta sẽ tối ưu hóa một chút về sao lưu dữ liệu trên Redis và cách sắp xếp dữ liệu trên file theo thời gian trong một dự án thực tế.

Hẹn gặp các bạn ở bài sau.

Bài viết liên quan

SQL trong Data Analysis: Procedure và Function – 2 công cụ không thể thiếu

Xin chào các bạn đã quay trở lại chuỗi bài SQL trong Data Analysis...

Tự học Data Analyst: Tổng hợp chuỗi bài SQL 101 trong Data Analysis

Trong bài viết này, chúng ta sẽ tổng hợp các bài viết thành một...

SQL trong Data Analysis: Hiểu rõ và ứng dụng đệ quy (Recursive trong PostgreSQL)

Trong thế giới của cơ sở dữ liệu quan hệ, các truy vấn đệ...

[Phân Tích Dữ Liệu Với Python] Tập 1: Làm Quen Với Pandas

Trong thời đại tiến bộ của khoa học dữ liệu, khả năng phân tích...
spot_img