2024/09/09

InfiniBand IB

作為網路互連的技術方案,Ethernet 跟 InfiniBand (IB) 兩者的發展目標不同,導致現在應用的領域跟範圍都不同。

  • 使用場景

一般人比較常聽到 Ethernet,Ethernet 用在區域網路中,可將多個網路設備連接起來,以光纖連接 Internet,以無線網路連接手持設備。InfiniteBand 主要用在HPC 與 data center,這類對高頻寬低延遲的網路連接需求較高的應用場景

  • 頻寬

Ethernet 的應用發展,並不是以高速頻寬為主要發展的重點,他的重點在讓多種異質網路終端能夠互相連接起來,所以頻寬並不是發展的重點。通常 Ethernet 是在 1Gbps 到 100 Gbps,而InfiniBand 可到 100 Gbps 或 200 Gbps,像這份報導的說明,InfiniBand進入400Gb/s世代,Nvidia新款網路交換器揭開序幕 | iThome 已經到了 400Gbps 的時代。

  • 應用範圍

Ethneret 面向一般終端的消費者,用來做個人使用的資料傳輸並連接 Internet。InfiniBand 是用在大量的資料運算,在現在最熱門的 AI 時代,要建構一個 AI cluster,會使用 InfiniBand 作為 cluster 節點的連接技術。

InfiniBand 另一個應用是在超級電腦裡面,因為超級電腦叢集也需要這種高速低延遲的資料傳輸。

  • 成本

InfiniBnad 的效能高,相對成本就很高,沒有特殊的需求,一般是不會使用 InfiniBand

  • 網路模式

InfiniBand 比 Ethernet 容易管理,每一個 end node 會透過一個 layer 2 switch 配置網路節點 ID,再往上以 Router 統計計算網路資料轉發路徑。

Ethernet 是用硬體的 MAC 網路模式,往上使用 IP 搭配 ARP 建構網路,且網路本身沒有學習機制,容易產生環狀網路,這又要透過 STP 協定解決,增加了網路的複雜度

Socket Direct Protocol (SDP)

Trail: Sockets Direct Protocol (The Java™ Tutorials)

Java 7 Sockets Direct Protocol – Write Once, Run Everywhere …. and Run (Some Places) Blazingly - InfoQ

SDP

在 JDK 7 裡面,就提供了一種不同於 TCP/IP 傳統的 Socket 網路的 Socket Direct Protocol,SDP 能夠透過 InifiBand 的 Remote Direct Memory Access (RDMA) ,以低延遲的方法,不透過 OS,遠端存取其他電腦的記憶體。

透過這張圖的說明,可以了解為什麼 SDP 能夠提供高速的通訊,傳統的 TCP/IP 需要一層一層接過 Application Layer -> Transport Layer -> Network Layer -> Physical Layer,SDP 則是從 Application 一步直接穿到 RDMA enabled channel adapter card

參考這個連結的圖片: SDP

References

InfiniBand - 維基百科,自由的百科全書

InfiniBand與以太網:它們是什麼? | 飛速(FS)社區

InfiniBand之技術架構介紹

2024/09/02

Quartz

Quartz 是 java 的 job-scheduling framwork,可使用類似 linux 的 crontab 方式設定定時啟動某個工作。

pom.xml

        <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.2</version>
        </dependency>

API

quartz API 的核心是 scheduler,啟動後,Scheduler 會產生多個 worker threads,也就是利用 ThreadPool,執行 Jobs。

主要的 API interface

  • Scheduler

  • Job

  • JobDetail

    • 定義 job instances
  • Trigger

    • 決定 scheduler 的定時機制

    • SimpleTrigger 跟 CronTrigger 兩種

  • JobBuilder

    • 用來產生 JobDetail instances
  • TriggerBuilder

    • 用來產生 Trigger instances

Example

SimpleJob.java

package quartz;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

// 要實作 Job interface
public class SimpleJob implements Job {
    // trigger 會透過 scheduler 的 worker 呼叫 execute 執行 job
    // JobExecutionContext 可提供 runtime environment 資訊
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 在 產生 JobDetail 時,JobData 會透過 JobDataMap 傳進 Job
        // Job 利用 JobDataMap 取得 JobData
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        String jobSays = dataMap.getString("jobSays");
        float myFloatValue = dataMap.getFloat("myFloatValue");

        System.out.println("Job says: " + jobSays + ", and val is: " + myFloatValue);
    }
}

JobA.java

@DisallowConcurrentExecution 可限制 Job 不會同時被執行兩次

package quartz;

import org.quartz.*;

@DisallowConcurrentExecution
public class JobA implements Job {

    public void execute(JobExecutionContext context) throws JobExecutionException {
        TriggerKey triggerKey= context.getTrigger().getKey();
        System.out.println("job A. triggerKey="+triggerKey.getName()+", group="+triggerKey.getGroup() + ", fireTime="+context.getFireTime());
    }

}

JobB.java

package quartz;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.TriggerKey;

public class JobB implements Job {

    public void execute(JobExecutionContext context) throws JobExecutionException {
        TriggerKey triggerKey= context.getTrigger().getKey();
        System.out.println("job B. triggerKey="+triggerKey.getName()+", group="+triggerKey.getGroup() + ", fireTime="+context.getFireTime());
    }

}

QuartzExample.java

package quartz;

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzExample {

    public static void main(String args[]) {
        try {
            // 透過 SchedulerFactory 產生 Scheduler
            SchedulerFactory schedFact = new StdSchedulerFactory();
            Scheduler sched = schedFact.getScheduler();
            // 使用 start 啟動,使用 shutdown 停掉這個 scheduler
            // 要先將 job, trigger 綁定 scheduler 後,再啟動 scheduler
            // sched.start();
            // sched.shutdown();

            // 產生 SimpleJob,利用 JobData 傳入資料到 Job
            JobDetail job = JobBuilder.newJob(SimpleJob.class)
                                      .withIdentity("myJob", "group1")
                                      .usingJobData("jobSays", "Hello World!")
                                      .usingJobData("myFloatValue", 3.141f)
                                      .build();

            Trigger trigger = TriggerBuilder.newTrigger()
                                            .withIdentity("myTrigger", "group1")
                                            .startNow()
                                            .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(40).repeatForever())
                                            .build();

            ///////
            JobDetail jobA = JobBuilder.newJob(JobA.class)
                                       .withIdentity("jobA", "group2")
                                       .build();
            JobDetail jobB = JobBuilder.newJob(JobB.class)
                                       .withIdentity("jobB", "group2")
                                       .build();

            // triggerA 比 triggerB 有較高的 priority,比較早先被執行
            // 每 40s 啟動一次
            Trigger triggerA = TriggerBuilder.newTrigger()
                                             .withIdentity("triggerA", "group2")
                                             .startNow()
                                             .withPriority(15)
                                             .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(40).repeatForever())
                                             .build();

            // 每 20s 啟動一次
            Trigger triggerB = TriggerBuilder.newTrigger()
                                             .withIdentity("triggerB", "group2")
                                             .startNow()
                                             .withPriority(10)
                                             .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(20).repeatForever())
                                             .build();

            sched.scheduleJob(job, trigger);
            sched.scheduleJob(jobA, triggerA);
            sched.scheduleJob(jobB, triggerB);
            sched.start();

        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

}

執行結果

Job says: Hello World!, and val is: 3.141
job A. triggerKey=triggerA, group=group2, fireTime=Wed Aug 28 16:16:37 CST 2024
job B. triggerKey=triggerB, group=group2, fireTime=Wed Aug 28 16:16:37 CST 2024
job B. triggerKey=triggerB, group=group2, fireTime=Wed Aug 28 16:16:57 CST 2024
Job says: Hello World!, and val is: 3.141
job A. triggerKey=triggerA, group=group2, fireTime=Wed Aug 28 16:17:17 CST 2024
job B. triggerKey=triggerB, group=group2, fireTime=Wed Aug 28 16:17:17 CST 2024

QuartzExample2.java

package quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzExample2 {

    public static void main(String args[]) {
        try {
            SchedulerFactory schedFact = new StdSchedulerFactory();
            Scheduler sched = schedFact.getScheduler();

            // 產生 SimpleJob,利用 JobData 傳入資料到 Job
            JobDetail job = JobBuilder.newJob(SimpleJob.class)
                                      .withIdentity("myJob", "group1")
                                      .usingJobData("jobSays", "Hello World!")
                                      .usingJobData("myFloatValue", 3.141f)
                                      .build();

            Trigger trigger = TriggerBuilder.newTrigger()
                                            .withIdentity("myTrigger", "group1")
                                            .startNow()
                                            .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(40).repeatForever())
                                            .build();

            // 跟上面的 Scheduler 一樣
            // 如果 worker thread 不足,job 可能不會被執行
            // 當 quartz 發現有這種狀況時,會使用 .withMisfireHandlingInstructionFireNow() 這個規則
            // 在 misfire 時,馬上執行一次
            Trigger trigger2 = TriggerBuilder.newTrigger()
                                            .withIdentity("myTrigger", "group1")
                                            .startNow()
                                            .withSchedule(SimpleScheduleBuilder.simpleSchedule().withMisfireHandlingInstructionFireNow().withIntervalInSeconds(40).repeatForever())
                                            .build();

            sched.scheduleJob(job, trigger);
            sched.start();

        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

}

QuartzExample3.java

package quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Date;

public class QuartzExample3 {

    public static void main(String args[]) {
        try {
            SchedulerFactory schedFact = new StdSchedulerFactory();
            Scheduler sched = schedFact.getScheduler();

            // 產生 SimpleJob,利用 JobData 傳入資料到 Job
            JobDetail jobA = JobBuilder.newJob(JobA.class)
                                       .withIdentity("jobA", "group2")
                                       .build();

            // SimpleTrigger 可設定在某個特定時間開始
            // 3 seconds 後啟動 trigger
            // 同樣可加上 SimpleScheduleBuilder 定時每 40s 啟動一次
            java.util.Calendar calendar = java.util.Calendar.getInstance();
            calendar.add(java.util.Calendar.SECOND, 3);
            Date myStartTime = calendar.getTime();
            SimpleTrigger trigger =
                    (SimpleTrigger) TriggerBuilder.newTrigger()
                                                  .withIdentity("trigger1", "group1")
                                                  .startAt(myStartTime)
                                                  .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(40).repeatForever())
                                                  .build();

            JobDetail jobB = JobBuilder.newJob(JobB.class)
                                       .withIdentity("jobB", "group1")
                                       .build();
            // 透過 CronScheduleBuilder 產生 scheduler
            // "0 0/1 * * * ?" 代表每分鐘執行一次
            // 然後產生 CronTrigger
            CronTrigger trigger2 = TriggerBuilder.newTrigger()
                                                 .withIdentity("trigger2", "group1")
                                                 .withSchedule(CronScheduleBuilder.cronSchedule("0 0/1 * * * ?"))
                                                 .build();

            sched.scheduleJob(jobA, trigger);
            sched.scheduleJob(jobB, trigger2);
            sched.start();

        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

}

執行結果

job B. triggerKey=trigger2, group=group1, fireTime=Wed Aug 28 16:11:00 CST 2024
job A. triggerKey=trigger1, group=group1, fireTime=Wed Aug 28 16:11:00 CST 2024
job A. triggerKey=trigger1, group=group1, fireTime=Wed Aug 28 16:11:40 CST 2024
job B. triggerKey=trigger2, group=group1, fireTime=Wed Aug 28 16:12:00 CST 2024
job A. triggerKey=trigger1, group=group1, fireTime=Wed Aug 28 16:12:20 CST 2024

References

Introduction to Quartz | Baeldung