2024年6月28日 星期五

Java 使用 ScheduledThreadPoolExecutor 執行定時任務

 Java 的 ScheduledThreadPoolExecutor 可以很方便地實現簡單的定時任務,
其中有兩個方法:

  1. 定時速率任務: scheduleAtFixedRate(command, initialDelay, period, unit)
  2. 固定延遲任務:scheduleWithFixedDelay(command, initialDelay, period, unit)

這兩個方法很相似,但又有點不同,
這邊做一下說明及練習記錄。

scheduleAtFixedRate 是定時速率任務,它會盡可能地以一定的時間間隔執行任務,
邏輯是像這樣:
1. 當任務開始時,開始等待時間間隔設定的時間。
2. 等完時間間隔了以後,檢查上個任務執行完成了沒有,如果執行完成了就直接執行下個任務,如果還沒執行完,就繼續等待任務完成後再執行下個任務,開始執行下個任務時回到第1步。

scheduleWithFixedDelay 是固定延遲任務,它會在每一次任務完成後,等待設定的時間間隔,然後再執行下一次任務,
邏輯是像這樣:

1. 當任務完成後,開始等待時間間隔設定的時間。
2. 等完時間間隔了以後,直接執行下一個任務,等任務完成後回到第1步。

下面直接展示範例並印出執行過程,可以更好地理解這兩個 method 的特性。

package test;

import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ScheduledTest {

	public static void main(String[] args) {
		ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(5);

		//定時速率任務:
		//ScheduledThreadPoolExecutor.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
		//在 initialDelay 過後開始執行第一次工作,
		//當開始執行工作時就會開始計算 period,如果 period 時間到了且上一個工作已做完了就會執行下一次工作,
		//如果 period 時間到了但上一個工作還未做完,就會等到上一個工作作完後馬上執行下一次工作。
		//此例為工作需花費3秒,period 設 1 秒,所以 period 時間到時上一個工作會還沒做完,
		//所以會等待上一個工作做完好直接執行下一個工作。
		scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
			DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxxx")
					                                 .withZone(ZoneId.of("+0000"));
			
			System.out.println(dtf.format(Instant.now()) + " --> test AtFixedRate - Start");
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(dtf.format(Instant.now()) + " --> test AtFixedRate - End");
		}, 0, 1, TimeUnit.SECONDS);
		/** 2024-06-28 08:11:07+00:00 --> test AtFixedRate - Start
			2024-06-28 08:11:10+00:00 --> test AtFixedRate - End
			2024-06-28 08:11:10+00:00 --> test AtFixedRate - Start
			2024-06-28 08:11:13+00:00 --> test AtFixedRate - End
			2024-06-28 08:11:13+00:00 --> test AtFixedRate - Start
			2024-06-28 08:11:16+00:00 --> test AtFixedRate - End
		*/		
		//此例為工作需花費3秒,period 設 5 秒,所以 period 時間到後上一個工作已經做完,
		//所以會直接執行下一個工作。
		scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
			DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxxx")
					                                 .withZone(ZoneId.of("+0000"));
			
			System.out.println(dtf.format(Instant.now()) + " --> test AtFixedRate - Start");
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(dtf.format(Instant.now()) + " --> test AtFixedRate - End");
		}, 0, 5, TimeUnit.SECONDS);
		/** 2024-06-28 08:12:30+00:00 --> test AtFixedRate - Start
			2024-06-28 08:12:33+00:00 --> test AtFixedRate - End
			2024-06-28 08:12:35+00:00 --> test AtFixedRate - Start
			2024-06-28 08:12:38+00:00 --> test AtFixedRate - End
			2024-06-28 08:12:40+00:00 --> test AtFixedRate - Start
			2024-06-28 08:12:43+00:00 --> test AtFixedRate - End
		*/
		
		//固定延遲任務:
		//ScheduledThreadPoolExecutor.scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
		//在 initialDelay 過後開始執行第一次工作,
		//工作執行完時才會就會開始計算 period,如果 period 時間到了就會執行下一次工作。
		//此例為工作需花費3秒,period 設 1 秒,所以 period 時間到上一個工作會還沒做完,
		//所以會等待上一個工作做完好直接執行下一個工作。
		scheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> {
			DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxxx")
					                                 .withZone(ZoneId.of("+0000"));
			
			System.out.println(dtf.format(Instant.now()) + " --> test WithFixedDelay - Start");
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(dtf.format(Instant.now()) + " --> test WithFixedDelay - End");
		}, 0, 1, TimeUnit.SECONDS);		
		/** 2024-06-28 08:14:39+00:00 --> test WithFixedDelay - Start
			2024-06-28 08:14:42+00:00 --> test WithFixedDelay - End
			2024-06-28 08:14:43+00:00 --> test WithFixedDelay - Start
			2024-06-28 08:14:46+00:00 --> test WithFixedDelay - End
			2024-06-28 08:14:47+00:00 --> test WithFixedDelay - Start
			2024-06-28 08:14:50+00:00 --> test WithFixedDelay - End
		*/
		//此例為工作需花費3秒,period 設 5 秒,待上一個工作結束後
		//會等待 period 的時間(也就是5秒)後執行下一個工作。
		scheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> {
			DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxxx")
					                                 .withZone(ZoneId.of("+0000"));
			
			System.out.println(dtf.format(Instant.now()) + " --> test WithFixedDelay - Start" );
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(dtf.format(Instant.now()) + " --> test WithFixedDelay - End" );
		}, 0, 5, TimeUnit.SECONDS);
		/** 2024-06-28 08:20:43+00:00 --> test WithFixedDelay - Start
			2024-06-28 08:20:46+00:00 --> test WithFixedDelay - End
			2024-06-28 08:20:51+00:00 --> test WithFixedDelay - Start
			2024-06-28 08:20:54+00:00 --> test WithFixedDelay - End
			2024-06-28 08:20:59+00:00 --> test WithFixedDelay - Start
			2024-06-28 08:21:02+00:00 --> test WithFixedDelay - End
		*/
		
	}

}


參考資料:

  1. Java高并发编程中ScheduledExecutorService的使用及详细介绍-刘宇
  2. ScheduledThreadPoolExecutor之scheduleWithFixedDelay和scheduleAtFixedRate的区别-CSDN博客

2024年6月24日 星期一

PostgreSQL 好用指令紀錄

#進人 PostgreSQL 終端:

psql --host={host} --port={post} --username={username} --dbname={dbname}

#離開 PostgreSQL 終端:

\q

#列出所有 Database

\l

#使用指定的 Database:

\c {Database 名稱} 

#列出所有的 Table:

\dt 

-------------------------------------------------------------------------------------------------------------

備份資料庫分兩種方式:使用 pg_dump 或 pg_dumpall 工具的 SQL 腳本備份,和使用 pg_basebackup 工具的 物理備份,以下說明:

--------------------------------------- SQL 腳本備份 ------------------------------------------------------------------

#使用 pg_dump 工具備份 Database 的 SQL 腳本,可以用 -U 指定 Database 的 User,其他參數可可參考官方文件

pg_dump {Database 名稱} -U {Database 的 User} > {自取的備份檔名路徑,例如 /usr/local/my_db_backup.sql}

#使用 pg_dumpall 工具備份所有資料庫和角色,可以用 -U 指定 Database 的 User ()

pg_dumpall -U {Database 的 User} > {自取的備份檔名路徑,例如 /usr/local/my_all_db_backup.sql}

#使用 pg_dump 工具備份時的資料庫還原方式

psql -U {user 名稱} -d {Database 名稱} < {自取的備份檔名路徑,例如 /usr/local/my_all_db_backup.sql}

#使用 pg_dumpall 工具備份時的資料庫還原方式

psql -U {user 名稱} -f {自取的備份檔名路徑,例如 /usr/local/my_all_db_backup.sql}

---------------------------------------- 物理備份 ---------------------------------------------------------------------


--------------------------------------------------------------------------------------------------------------------------

#使用 pg_basebackup 工具進行物理備份,以下指令可以得到幾個檔案,例如 backup_manifest、 base.tar.gz 、 pg_wal.tar.gz,其中我們需要的是 base.tar.gz:

pg_basebackup -U {user 名} -D {自行決定的備份資料夾路徑,例如 /usr/local/my_db_backup} -F t -Z 9 -P -v

各參數意思: 

  • -D 或 --pgdata:指定備份的目標目錄。
  •  -F 或 --format:指定輸出格式,可以是純文本(p)或 tar 格式(t)。
  •  -Z:指定壓縮級別,範圍是 0(無壓縮)到 9(最大壓縮)。 
  • -P 或 --progress:顯示進度信息。 -v 或 --verbose:顯示詳細的命令行輸出。

#用使用 pg_basebackup 工具進行的物理備份來還原:

方式是直接把整個物理備份的資料夾取代原來 PostgreSQL 的資料夾

先停止 PostgreSQL 服務

systemctl stop postgresql

先把原來的 PostgreSQL 資料夾移去別處,如果有問題可以用來還原回來

mv /var/lib/postgresql/data var/lib/postgresql/data.bak

Note: PostGreSQL 資料夾位置可能不一定一樣,跟使用的環境、版本有關

將物理備份的 base.tar.gz 解壓縮後將資料複製一份到 PostgreSQL 原資料夾路徑

cp /usr/local/my_db_backup/base.tar.gz /var/lib/postgresql/data
tar -xvzf /var/lib/postgresql/data/base.tar.gz

設定 owner 所有權:

chown -R {owner 名}:{owner 名} /var/lib/postgresql/data

再啟動 PostgreSQL 服務

systemctl start postgresql

參考資料:

  1. Day 18 PostgreSQL 資料庫備份和恢復
  2. PostgreSQL: Documentation: 16: pg_dump