create table task_init (--任務初始表
uidint,--用戶id
ptid serial8,--父任務id
tid serial,--子任務ID
state int default 1,--任務狀態,1表示初始狀態,-1表示正在處理, 0表示處理結束
retry int default -1,--重試次數
info text,--其他資訊
ts timestamp--時間
);
2、任務歷史表,用於隱藏任務的最終狀態。
create table task_hist (--任務歷史表
uidint,--用戶id
ptid int8,--父任務id
tid int,--子任務ID
state int default 1,--任務狀態,1表示初始狀態,-1表示正在處理, 0表示處理結束
retry int default -1,--重試次數
info text,--其他資訊
ts timestamp--時間
);
3、為了簡化測試,按用戶ID進行分區。(前面提到的rotate設計,多級分區設計,請參考本文末尾的本文)
do language plpgsql $$
declare
begin
for i in 1..1000 loop
execute 'create table task_init_'||i||' ( like task_init including all)';
execute 'create table task_hist_'||i||' ( like task_hist including all)';
end loop;
end;
$$;
4、為了測試方便,使用schemaless的設計,將用戶任務的初始資料生成寫入放在PLPGSQL邏輯中。
create or replace function ins_task_init(
uid int,
info text,
ts timestamp
)returns void as $$
declare
target name;
begin
target := format('%I', 'task_init_'||uid);
execute format('insert into %I (uid,info,ts) values (%L,%L,%L)', target, uid,info,ts);
end;
$$ language plpgsql strict;
5、執行任務,分為幾個步驟。
5.1、從任務表讀取任務。
5.2、用戶執行任務。
5.3、回饋執行的結果,不成功的任務更新task_init表,對於執行成功(並結束)的任務,資料從task_init遷移到task_hist。
為了測試資料庫的效能,我講這三步的邏輯寫到plpgsql裡面。同時使用delete limit的屬性,一次批量取出若干條任務。
這裡使用CTID行號尋找,達到最佳的效能。不僅免去了索引的使用,而且效能更佳。
這裡使用了advisory lock,使得單個用戶不會出現並行任務。(實際商務中,可以並行。)
這裡沒有測試更新狀態,task_init還有少量更新(相比insert和delete,比例很少,可以忽略),比如任務失敗的情況。
關閉task_init表的autovacuum,採用rotate的形式進行處理。
create or replace function run_task(
uid int,
batch int
) returns void as $$
declare
target1 name;
target2 name;
begin
target1 := format('%I', 'task_init_'||uid);
target2 := format('%I', 'task_hist_'||uid);
execute format('with t1 as (select ctid from %I where pg_try_advisory_xact_lock(%L) limit %s) , t2 as (delete from %I where ctid = any (array(select ctid from t1)) returning *)insert into %I select * from t2;', target1, uid, batch, target1, target2);
end;
$$ language plpgsql strict;
6、測試分解動作。
寫入初始任務
postgres=# select ins_task_init(1,'test',now()::timestamp);
ins_task_init
---------------
(1 row)
postgres=# select ins_task_init(1,'test',now()::timestamp);
ins_task_init
---------------
(1 row)
執行任務
postgres=# select run_task(1,100);
run_task
----------
(1 row)
查看任務是否結束並遷移到歷史表
postgres=# select * from task_init_1;
uid | ptid | tid | state | retry | info | ts
-----+------+-----+-------+-------+------+----
(0 rows)
postgres=# select * from task_hist_1;
uid | ptid | tid | state | retry | info |ts
-----+------+-----+-------+-------+------+----------------------------
1 |1 |1 |1 |-1 | test | 2017-07-20 15:26:32.739766
1 |2 |2 |1 |-1 | test | 2017-07-20 15:26:33.233469
(2 rows)
效能壓測
1、生成任務的效能
vi ins.sql
set uid random(1,1000)
select ins_task_init(:uid,'test',now()::timestamp);
pgbench -M prepared -n -r -P 1 -f ./ins.sql -c 32 -j 32 -T 120
query mode: prepared
number of clients: 64
number of threads: 64
duration: 360 s
number of transactions actually processed: 86074880
latency average = 0.268 ms
latency stddev = 0.295 ms
tps = 239079.558174 (including connections establishing)
tps = 239088.708200 (excluding connections establishing)
script statistics:
- statement latencies in milliseconds:
0.001set uid random(1,1000)
0.267select ins_task_init(:uid,'test',now()::timestamp);
postgres=# select count(*) from task_init_1;
count
-------
88861
(1 row)
postgres=# select count(*) from task_init_2;
count
-------
88196
(1 row)
....
postgres=# select count(*) from task_init_1000;
count
-------
88468
(1 row)
2、執行任務的效能(一次批量取10000條任務)
vi run.sql
set uid random(1,1000)
select run_task(:uid,10000);
pgbench -M prepared -n -r -P 1 -f ./run.sql -c 32 -j 32 -T 120
query mode: prepared
number of clients: 32
number of threads: 32
duration: 120 s
number of transactions actually processed: 3294
latency average = 1171.228 ms
latency stddev = 361.056 ms
tps = 27.245606 (including connections establishing)
tps = 27.247560 (excluding connections establishing)
script statistics:
- statement latencies in milliseconds:
0.003set uid random(1,1000)
1171.225select run_task(:uid,10000);
postgres=# select count(*) from task_init_1000;
count
-------
18468
(1 row)
postgres=# select count(*) from task_hist_1000;
count
--------
224207
(1 row)
單獨的測試資料
1、生成任務,23.9萬條/s
2、消耗任務,27.2萬條/s
生成與消耗任務同時啟動並執行測試資料
1、生成任務,16.8萬條/s
2、消耗任務,大於16.8萬條/s
沒有任何任務堆積。
小結
PostgreSQL在雲端海量任務調度系統中,發揮了重要的作用。
單個PostgreSQL實例,已經可以處理每個小時 的任務生成,以及 的任務消耗。
任務調度系統比MQ更加複雜,類似MQ的超集合,所以使用者如果有MQ的需求,實際上使用RDS PostgreSQL也是可以的。效能指標比上面的測試更好。
參考