Thursday, April 13, 2023

001: Message-driven programming với Message broker và Apache Kafka

1) Asynchrounous và Message-driven programming

Nếu đọc bài viết này thì chắc chắn chúng ta đã nắm chắc các khái niệm cơ bản về async programming và message-driven. Còn nếu chưa thì.. đọc tiếp nhé.

1.1) Asynchronous programming

Làm một chiếc ví dụ cho sôi động:

Sau khi release project thành công, leader quyết định cho cả team đi ăn... KFC. Thôi cũng được, đi ăn không mất tiền là sướng rồi.

Các anh em nhanh chóng phi đến nơi và tự phân chia vào các hàng chờ để đến lượt để gọi món. Người đến sau chờ người đến trước, ông nào lớ ngớ ra khỏi hàng cái là lại xuống cuối, mất chỗ như chơi.

Ông nào mà vã quá có khi lại đứng tán tỉnh luôn em bán hàng mất đến 15p thì đúng là.. há mồm.

Thấy nhân viên công ty F đông quá, giám đốc chi nhánh quyết định tăng gấp đôi số lượng line, tốc độ cải thiện rõ rệt. Tuy nhiên vẫn không tránh được việc một số anh em tranh thủ xin số em nhân viên...

Hãy coi các anh em công ty F là client và các em nhân viên là server. Trong ví dụ này, client phải tương tác trực tiếp với server, và đó là hình ảnh của synchronous programming - lập trình đồng bộ. Client cần chờ cho đến khi server xử lý xong request để tiếp tục chạy. Như vậy, mấu chốt của synchronous programming không nằm ở số lượng client, số lượng request hay số lượng server mà nằm ở cách client và server tương tác với nhau.

Async programming mình đang đề cập là trong việc giao tiếp giữa nhiều service với nhau, không phải trong single application với multi-thread. Anw, idea của nó na ná nhau, đều muốn tránh blocking khi xử lý request.

Nếu chỉ cần cải thiện tốc độ thì không khó, cứ làm theo giải pháp của giám đốc chi nhánh là ok, tăng số lượng nhân viên bằng số lượng khách hàng.

Và dĩ nhiên trong thực tế chẳng ai làm như thế 🔨. Và nó cũng không giải quyết được vấn đề giao tiếp trực tiếp, chờ đợi lẫn nhau:

  • Nhân viên: ...
  • Khách hàng: ...
  • Nhân viên: ...
  • Khách hàng: ...
  • Nhân viên: ...

Mọi chuyện tưởng chừng không có vấn đề gì, nhưng sẽ có 2 bất lợi chính:

  • Nếu server có gặp sự cố, client phải chờ cho đến khi server phục hồi và trả lời request hoặc quá timeout, và không chỉ một mà còn rất nhiều client phía sau cũng phải chờ.
  • Client phải chờ phản hồi từ server mới có thể tiếp tục thực hiện các công việc khác.

Chuyện gì cũng có cách giải quyết. Vậy giám đốc chi nhánh xử lý thế nào?

Nhà hàng nhận thấy điểm yếu trong khâu order. Họ quyết định chuyển sang hình thức khác. Sau khi lựa chọn được các món ăn ưa thích, khách hàng sẽ thông báo cho nhân viên. Trong lúc nhân viên ghi chép tính toán thì khách hàng có thể đi tìm chỗ ngồi và làm việc cá nhân. Sau khi xác nhận đơn hàng hàng công, nhân viên báo lại cho khách hàng ra thanh toán và nhận đồ.

Vấn đề được giải quyết, client và server vẫn giao tiếp trực tiếp nhưng không chờ đợi nhau. Client có thể làm việc riêng tùy thích trong khoảng thời gian đó, và được thông báo khi server xử lý xong yêu cầu.

Đó là ví dụ của asynchronous programming. Như vậy, việc giải quyết bài toán không nằm ở cách thức giao tiếp hay kênh giao tiếp mà giải pháp làtách biệt request và response.

Tách biệt hai luồng request và response, nghĩa là hoàn toàn có thể thực hiện giao tiếp trực tiếp giữa nhiều service với nhau.

  • Service A gửi request đến Service B.
  • Service B nhận request và phản hồi đã nhận được request. Để đó xử lý sau.
  • Trong lúc đó, Service A hoàn toàn rảnh rang thực hiện các công việc khác.
  • Sau khi Service B xử lý xong request sẽ gửi ngược lại cho Service A.

Tuy nhiên chưa thực sự triệt để, vẫn có thể gặp 2 vấn đề:

  • Nếu server quá tải từ chối nhận thêm request, hoặc server.. die. Client cần cơ chế retry để gửi lại request.
  • Trong trường hợp muốn order ở tất cả cửa hàng tại Hà Nội. Khách hàng cần gửi request đến từng cửa hàng.

1.2) Message-driven programming

Ca này hơi khó, nhà hàng quyết định thuê các chuyên gia về tìm giải pháp gỡ rối.

Sau một hồi xem xét, các chuyên gia đều đồng tình phán một câu xanh rờn: việc gì mà phức tạp thì outsource cho nhanh.

Những tưởng chỉ là câu nói đùa nhưng vấn đề đã được giải quyết êm đẹp. Nhà hàng thay đổi cách thức hoạt động, khách hàng thực hiện order qua hệ thống SMS. Sau đó hệ thống SMS điều phối request đến các cửa hàng, nhân viên để thực hiện nhiệm vụ. Không lo việc chẳng may một nhà hàng bị đóng cửa thì đã có nhà hàng khác phục vụ. Hoặc muốn thử đồ ăn ở nhiều nơi thì không cần gửi một list các request mà chỉ cần gửi một request. Các nhiệm vụ còn lại là công việc của hệ thống SMS.

Ngoài ra, khách hàng và nhân viên không cần biết nhau, tránh việc khách hàng đánh nhân viên u đầu vì món ăn quá tệ 🔨.

Đấy là mình lấy ví dụ thôi chứ nhà hàng nào mà áp dụng hình thức này chắc phá sản 😂. Trong thực tế hệ thống message broker là Grab, Now, Baemin... với các chàng shipper đẹp zai lực lưỡng.

Message-driven programming bắt nguồn từ tư tưởng trên. Server và client không giao tiếp trực tiếp với nhau nữa. Tất cả các request sẽ được gửi dưới dạng message cho bên thứ 3. Bên thứ 3 có nhiệm vụ điều hướng các message đến địa chỉ cụ thể với 2 mục tiêu:

  • Đảm bảo gửi message thành công.
  • Và gửi đến đúng địa chỉ.

2) Message broker

Với ví dụ trên, hệ thống SMS là một Message Broker với mục đích điều hướng, trung chuyển message từ người gửi đến người nhận, với 4 ưu điểm:

  • Giảm tải cho các server bằng việc giảm các tương tác trực tiếp.
  • Lưu trữ request, trong trường hợp server gặp sự cố.
  • Phân phối request đến các nhiều server trong các bài toán cụ thể.
  • Đơn giản hóa quá trình gửi nhận message trong môi trường multi-services.

No silver bullet, nhìn có vẻ gọn gàng nhưng cũng có hạn chế nhất định tuy nhiên không đề cập quá kĩ trong bài viết này 🙉.

  • Đảm bảo việc gửi nhận message đến đích.
  • Cần monitor thêm cả hệ thống Message broker.
  • Xử lý vấn đề khi Message broker gặp lỗi.
  • Tăng latency, giảm performance.
  • Vân vân và mây mây...

Hiện nay, có khá nhiều Message broker hoạt động dựa trên cách thức và nền tảng khác nhau, nhưng tựu chung lại đều chung mục đích điều hướng, trung chuyển message:

  • Apache ActiveMQ.
  • Apache RocketMQ.
  • RabbitMQ.
  • Apache Kafka.
  • IronMQ.
  • ZeroMQ.
  • Redis, thực tế hiếm khi sử dụng. Chẳng ai đi KIA Morning trên cao tốc trong khi đã có Lamborghini.

Nói chung là, nhiều loại vcđ... Chọn cái gì cũng cần đau đầu để nghĩ. Chọn sai là phải trả giá, đắt hay rẻ tùy thuộc túi tiền của bạn.

3) Message distribution patterns

Tương tự với ví dụ cửa hàng KFC, Message broker cung cấp 2 patterns chính để cung cấp việc điều hướng message:

  • Point-to-point messaging: hay còn gọi là Queue. Hiểu đơn giản đó là dạng phân phối message có quan hệ 1 - 1 giữa client và server, tao chỉ nói cho.. một mình mày thôi đấy. Mỗi message chỉ được gửi đến một endpoint duy nhất. Ví dụ là cuộc trò chuyện trên Skype giữa 2 người với nhau.
  • Broadcast messaging: một message có thể được gửi tới nhiều địa chỉ khác nhau, chỉ những người subcribe nội dung đó mới nhận được message. Ví dụ như khi follow mình, hệ thống chỉ gửi thông báo khi mình có bài viết mới đến các followers. Pattern này được gọi là Topic.

Với từng bài toán khác nhau ta sẽ linh hoạt sử dụng queue hoặc topic để xử lý vấn đề.

  • Nhắn tin hai người có thể dùng queue.
  • Khi nhắn tin trong group thì dùng topic.

4) Mô hình sử dụng Message broker

Message broker được đặt trong hệ thống bao gồm:

  • Producer/Publisher: nơi gửi message.
  • Message broker: hệ thống điều hướng message.
  • Consumer/Subcriber: nơi nhận message.

Không còn khái niệm client và server mà thay vào đó là producer/publisher và consumer/subscriber. Về bản chất vẫn như nhau, một bên gửi và một bên nhận message.

5) Phân chia Message broker theo cách thức hoạt động

Các chuyên gia phân chia Message broker ra thành 2 loại dựa trên cách thức hoạt động của chúng:

  • Message base.
  • Data pipline.
Message baseData pipeline
ActiveMQ, RabbitMQ, ZeroMQRocketMQ, Kafka
Lưu trạng thái của Consumer để đảm bảo tất cả đều nhận được message từ topic đang subscribe.Không lưu trạng thái của Consumer.
Message bị xóa sau khi các Consumer nhận được message.Message chưa bị xóa ngay sau khi Consumer nhận message.
Khi có message mới, Consumer chỉ lấy được duy nhất message đó.Consumer có thể tùy ý lựa chọn lấy về một danh sách các message, bao gồm cả message cũ.

Có thể quyết định sử dụng loại Message broker nào dựa trên bảng so sánh trên:

  • Với các bài toán yêu cầu đảm bảo Consumer đều nhận được một message và duy nhất một lần nhận là quan trọng nhất, ta sử dụng Message base.
  • Các bài toán yêu cầu sự chính xác cao và đảm bảo không lost message thì cân nhắc sử dụng Data pipeline.

Mạnh miệng thế chứ hiện tại chúng ta đa số đều sử dụng Apache Kafka, với hai lý do chính:

  • Mạnh mẽ, được quảng cáo performance tốt. Feature được nhấn mạnh là không mất message.
  • Công nghệ mới, tội gì không làm.

Reference

Reference in series https://viblo.asia/s/apache-kafka-tu-zero-den-one-aGK7jPbA5j2

Wednesday, November 30, 2016

Cài đặt thuật toán Dijskstra bằng java

Bố cuc:
1. Lớp Bài toán: Cài đặt thuật toán Dijstra
2.Lớp test

1 .Lớp cài đặt thuật toán Dijkstra
BaiToan.java
package baocaovantruhoc_nhom13;

/**
 *
 * @TranVanLinh
 */
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;

public class BaiToan
{
    int soDinh;//so dinh cua do thi
    int G[][];//ma tran trong so
    int diemDau;//diem dau
    int doDai[];//do dai toi dinh i
    int daXet[];// danh dau la dinh co duong di la ngan nhat
    int dinhTruoc[];//luu vet dinh truoc
    int oo = 0;//gia tri vo cung
    public BaiToan()
    {
        soDinh = 0;
        G = null;
        doDai = null;
        daXet=null;
        dinhTruoc = null;
        diemDau = 0;
        oo = 0;
    }
    //Doc du lieu
    public boolean getDuLieuTuFile(String tenfile)
    {
        String chuoifile[]=tenfile.split(".");
        String path="G://"+tenfile;
        try
        {
            File file=new File(path);
            if(!file.exists())
            {
                return false;
            }
            FileInputStream input = new FileInputStream(path);
            InputStreamReader istream=new InputStreamReader(input);
            BufferedReader reader=new BufferedReader(istream);
            String sc;
            //doc so dinh cua do thi , dinh bat dau , dinh dich
            sc = reader.readLine();
            String temp[] = sc.split(" ");
            soDinh = Integer.parseInt(temp[0]);
            diemDau = Integer.parseInt(temp[1]);
            G=new int[soDinh][soDinh];
            int row=0;
            //doc matran do thi
            while ((sc = reader.readLine()) != null)
            {
                temp = sc.split(" ");
                for (int col = 0; col < soDinh; col++)
                {
                     G[row][col] = Integer.parseInt(temp[col]);
                }
                row++;
            }
          
            //dong file
            reader.close();
            istream.close();
            input.close();
        }
        catch(FileNotFoundException ex)
        {
            System.err.println("LOi file");
        }
        catch (IOException ex)
        {
                System.err.println("Ngoai le xay ra.!");
        }
        return true;
    }
    public void thuatToan_Dijkstra()
    {
        oo=999999999;
        //Gan trong so cho cac canh khong co duong di la vong cung
        for (int i = 0; i < soDinh; i++)
        {
            for (int j = 0; j < soDinh; j++)
            {
                if (i != j && G[i][j] == 0)
                {
                    G[i][j] = oo;
                }
            }
        }
        diemDau--;
        doDai= new int[soDinh];
        daXet=new int[soDinh];
        dinhTruoc=new int[soDinh];
        for (int i = 0; i < soDinh; i++)
        {
            doDai[i] = oo;                   // khoi tao do dai tu a toi moi dinh la vo cung
            daXet[i] = 0;                       // danh sach cac diem da xet
            dinhTruoc[i] = diemDau;                   // dat diem bat dau cua moi diem la a
        }
        int i=0;
         //Khoi tao d(diemDau,diemDau)=0;
        doDai[diemDau] = 0;
        for(int dinh=0;dinh<soDinh;dinh++)
        {
               for (i = 0; i < soDinh; i++)   // tim mot diem chua xet ma co duong di < vo cung
               {
                   if (daXet[i] != 1 && doDai[i] < oo)
                   {
                       break;
                   }
               }
               if(i==soDinh)//khong co dinh nao thoa man
               {
                   break;
               }
               for (int j = 0; j < soDinh; j++)// tim dinh ma co do dai la nho nhat
               {  
                   if (daXet[j]!=1 && doDai[i] > doDai[j])
                   {
                       i = j;
                   }
               }
               daXet[i] = 1;    // cho i vao danh sach xet roi
         
               for (int j = 0; j < soDinh; j++) // tinh lai do dai cua cac diem chua xet
               {  
                   if (daXet[j]!=1 && doDai[i] + G[i][j] < doDai[j])
                   {
                       doDai[j] = doDai[i] + G[i][j];   // thay doi do dai cua d[i,j]
                       dinhTruoc[j] = i;                       // danh dau diem truoc j la i
                   }
               }
        }
    }
    public  void inDuongDi()
    {
        for(int dinh=0;dinh<soDinh;dinh++)
        if(dinh!=diemDau)
        {
             //in duong di
            if(doDai[dinh] < oo)
            {
                System.out.print("\t\tĐộ dài đường đi từ đỉnh " + ((char)(diemDau+65)) + " đến đỉnh " +  ((char)(dinh+65)) + " là: " + doDai[dinh]+"\t----");
                int mang[] = new int[soDinh];
                int dem = 0;
                int i=dinh;
                while (i != diemDau)
                {
                    mang[dem++] = i;
                    i = dinhTruoc[i];
                }
                System.out.print("\tChi tiết: " +  ((char)(diemDau+65)));
                for (int k = dem - 1; k >= 0; k--) {
                    System.out.print("-->" + (char)(mang[k]+65));
                }
            }
            else
            {
                System.out.println("\t\tKhông có đường đi từ đỉnh "+( ((char)(diemDau+65)))+" đến đỉnh " + ( ((char)(dinh+65))));
            }
            System.out.println("\n");
        }
    }
    public  void inDuong2Di()
    {
        System.out.println("Nhap dinh dich: ");
        Scanner sc=new Scanner(System.in);
        int diemCuoi=sc.nextInt();
      
     
             //in duong di
            if(doDai[diemCuoi] < oo)
            {
                System.out.print("\t\tĐộ dài đường đi từ đỉnh " + ((char)(diemDau+65)) + " đến đỉnh " +  ((char)(diemCuoi+65)) + " là: " + doDai[diemCuoi]+"\t----");
                int mang[] = new int[soDinh];
                int dem = 0;
                int i=diemCuoi;
                while (i != diemDau)
                {
                    mang[dem++] = i;
                    i = dinhTruoc[i];
                }
                System.out.print("\tChi tiết: " +  ((char)(diemDau+65)));
                for (int k = dem - 1; k >= 0; k--) {
                    System.out.print("-->" + (char)(mang[k]+65));
                }
            }
            else
            {
                System.out.println("\t\tKhông có đường đi từ đỉnh "+( ((char)(diemDau+65)))+" đến đỉnh " + ( ((char)(diemCuoi+65))));
            }
            System.out.println("\n");
      
    }
    public void inMatran()
    {
        for(int i=0;i<soDinh;i++)
        {
            System.out.print("\t\t");
            for(int j=0;j<soDinh;j++)
            {
                if(G[i][j]==0)
                {
                    System.out.print("oo\t");
                }
                else
                {
                    System.out.print(G[i][j]+"\t");
                }
            }
            System.out.println();
        }
        System.out.println();
    }
}

2. Lớp test 
BaoCaoVanTruHoc_Nhom13.java
 package baocaovantruhoc_nhom13;
import java.util.Scanner;

/**
 *
 * @TranVanLinh
 *
 */
public class BaoCaoVanTruHoc_Nhom13 {

    public static void main(String[] args)
    {
        System.out.println("\t\t\tBáo Cáo Bài Tập Lớn Môn Vận Trù Học");
        System.out.println("\t\tNhóm 13\tĐề tài: Cài đặt thuật toán Dijkstra\n");
       
        String tenTapTin;
        BaiToan thuatToan;
        do
        {
            System.out.print("\t\tNhập tên tập tin (.txt) : ");
            tenTapTin=(new Scanner(System.in)).nextLine();
            System.out.println("");
            thuatToan=new BaiToan();
            if(!thuatToan.getDuLieuTuFile(tenTapTin))
            {
                System.out.println("\t\tTập tin không tồn tại, Nhập lại.!");
            }
        }while(!thuatToan.getDuLieuTuFile(tenTapTin));
        System.out.println("\t\tMa trận kề : ");
        thuatToan.inMatran();
        thuatToan.thuatToan_Dijkstra();
        thuatToan.inDuongDi();
    }
}

Vd: Tìm đường đi của từ đỉnh x1 đến tất cả các đỉnh của đồ thị sau:



File input2.txt

 7 4
0 3 5 4 2 7 5
3 0 0 0 2 0 0
5 0 0 4 0 3 0
4 0 4 0 0 0 2
2 2 0 0 0 0 4
7 0 3 0 0 0 6
5 0 0 2 4 6 0


Kết quả:



Friday, November 11, 2016

Lập trình hướng đối tượng với Java số 11

1. Lớp HinhCN

public class HinhCN implements Comparable
{
    private double cdai;
    private double crong;
   
    public HinhCN()
    {
    }
    public HinhCN(double cdai,double crong)
    {
        this.cdai=cdai;
        this.crong=crong;
    }
   
    public double getCRong()
    {
        return crong;
    }
     public double getCDai()
    {
        return cdai;
    }
  public void setCRong(double a)
    {
          this.crong=a;
    }
     public void setCDai(double a))
    {
         this.cdai=a;

    }
    public double tinhDT()
    {
        return cdai*crong;
    }
    public double tinhCV()
    {
        return (cdai+crong)*2;
    }

    public boolean equals(HinhCN h2)
    {
        if(this.tinhDT()==h2.tinhDT())
        {
            return true;
        }
        return false;
    }
   
    public int compareTo(Object h2)
    {
        HinhCN hcn=(HinhCN)h2;
        if(this.tinhDT()<hcn.tinhDT())
        {
            return -1;
        }
        if(this.tinhDT()==hcn.tinhDT())
        {
            return 0;
        }
        return 1;
    }
}

2.Lớp test

public class Test
{
    public static void main(String [] args)
    {
        HinhCN a=new HinhCN(5,6);
        HinhCN b=new HinhCN(5,6);
        System.out.println("Su dung phuong thuc equals: ");
        if(a.equals(b))
        {
            System.out.println("Dien tich hai hinh bang nhau");
        }
        else
        {
            System.out.println("Dien tich hai hinh khong bang nhau");
        }
       
        HinhCN c=new HinhCN(9,9);
        HinhCN d=new HinhCN(10,9);
        System.out.println("Su dung phuong thuc compareTo: ");
        if(c.compareTo(d)==-1)
        {
            System.out.println("Dien tich c<d");
        }
        else if(c.compareTo(d)==0)
        {
            System.out.println("Dien tich c=d");
        }else
        {
             System.out.println("Dien tich c>d");
        }
    }
}

3.Kết quả: