このようなものを実装するのはおそらく10回目であり、思いついたソリューションに100%満足したことは一度もありません。
「適切な」メッセージングシステムの代わりにmysqlテーブルを使用する理由は、主に、ほとんどのアプリケーションがすでに他の作業にリレーショナルデータベースを使用しているためです(これまで行ってきたほとんどの作業ではmysqlになる傾向があります)。メッセージングシステムを使用します。また、リレーショナルデータベースには非常に強力なACIDプロパティがありますが、メッセージングシステムにはないことがよくあります。
最初のアイデアは使用することです:
テーブルジョブの作成( id auto_increment not null primary key、 message text not null、 process_id varbinary(255)null default null、 キーjobs_key(process_id) );
そして、エンキューは次のようになります。
jobs(message)に挿入values( 'blah blah');
そして、デキューは次のようになります。
begin; select * from jobs where process_id is null order by id asc limit 1; update jobs set process_id =?ここで、id = ?; -取得したものは何でも commit; -(id、message)をアプリケーションに戻し、完了後にクリーンアップします
テーブルとエンキューは見栄えがしますが、デキューはちょっと気になります。ロールバックする可能性はどのくらいありますか?またはブロックされますか? O(1)っぽくするためにどのキーを使うべきですか?
それとも私がやっていることよりも良い解決策はありますか?
私はいくつかのメッセージキューイングシステムを構築しましたが、どのタイプのメッセージを参照しているかはわかりませんが、デキューイングの場合(それは単語ですか?)、あなたが行ったのと同じことを行いました。 。あなたの方法は、シンプルでクリーンでしっかりしているように見えます。私の仕事が最高というわけではありませんが、多くのサイトの大規模な監視に非常に効果的であることが証明されています。 (エラーロギング、大量の電子メールマーケティングキャンペーン、ソーシャルネットワーキングの通知)
私の投票:心配ありません!
デキューはより簡潔になる可能性があります。トランザクションのロールバックに依存するのではなく、明示的なトランザクションなしで1つのアトミックステートメントでそれを行うことができます。
UPDATE jobs SET process_id = ? WHERE process_id IS NULL ORDER BY ID ASC LIMIT 1;
次に、(括弧[]は、詳細に応じてオプションを意味します)を使用してジョブをプルできます。
SELECT * FROM jobs WHERE process_id = ? [ORDER BY ID LIMIT 1];
Brian Akerが キューエンジン について少し前に話しました。 SELECT table FROM DELETE
構文についても話がありました。
スループットが気にならない場合は、いつでも SELECT GET_LOCK() をミューテックスとして使用できます。例えば:
SELECT GET_LOCK('READQUEUE');
SELECT * FROM jobs;
DELETE FROM JOBS WHERE ID = ?;
SELECT RELEASE_LOCK('READQUEUE');
そして、本当に凝ったものにしたい場合は、ストアドプロシージャでラップしてください。
これが私が使用した解決策で、現在のスレッドのprocess_idなしで作業するか、テーブルをロックします。
SELECT * from jobs ORDER BY ID ASC LIMIT 0,1;
$ row配列で結果を取得し、次を実行します。
DELETE from jobs WHERE ID=$row['ID'];
次に、影響を受ける行(mysql_affected_rows)を取得します。影響を受ける行がある場合は、$ row配列でジョブを処理します。影響を受ける行が0の場合は、他のプロセスが選択したジョブをすでに処理していることを意味します。行がなくなるまで、上記の手順を繰り返します。
私はこれを100k行の「jobs」テーブルでテストし、上記を実行する20の並行プロセスを生成しました。競合状態は発生しませんでした。上記のクエリを変更して、処理フラグで行を更新し、実際に処理した後で行を削除できます。
while(time()-$startTime<$timeout)
{
SELECT * from jobs WHERE processing is NULL ORDER BY ID ASC LIMIT 0,1;
if (count($row)==0) break;
UPDATE jobs set processing=1 WHERE ID=$row['ID'];
if (mysql_affected_rows==0) continue;
//process your job here
DELETE from jobs WHERE ID=$row['ID'];
}
言うまでもなく、この種の作業には適切なメッセージキュー(ActiveMQ、RabbitMQなど)を使用する必要があります。ただし、ホストはソフトウェアの更新時に定期的に問題を解決するため、このソリューションに頼らざるを得ませんでした。
このスレッド マッピング可能な設計情報があります。
引用するには:
これが私が過去にうまく使ったものです:
MsgQueueテーブルスキーマ
MsgId ID-NOT NULL
MsgTypeCode varchar(20)-NOT NULL
SourceCode varchar(20)-メッセージを挿入するプロセス-NULL可能
State char(1)-'キューに入れられた場合はN'ew、処理中の場合は' A '(ctive)、' C '完了、デフォルト' N'-NOT NULL
CreateTime datetime-デフォルトのGETDATE()-NOT NULL
Msg varchar(255)-NULL可能
あなたのメッセージタイプはあなたが期待するものです-挿入するプロセスと読み取るプロセスの間の契約に準拠し、XMLまたは他の表現の選択で構造化されたメッセージ(JSONは場合によっては便利ですインスタンス)。
次に、0からnのプロセスが挿入され、0からnのプロセスがメッセージの読み取りと処理を行うことができます。通常、各読み取りプロセスは単一のメッセージタイプを処理します。プロセスタイプの複数のインスタンスを実行して、負荷分散を行うことができます。
リーダーは1つのメッセージをプルし、メッセージの処理中に状態を「A」アクティブに変更します。完了すると、状態が「C」完了に変わります。監査証跡を保持するかどうかに応じて、メッセージを削除するかどうかを指定できます。 State = 'N'のメッセージは、MsgType/Timestampの順序でプルされるため、MsgType + State + CreateTimeにインデックスがあります。
バリエーション:
「E」エラーの状態。
リーダープロセスコードの列。
状態遷移のタイムスタンプ。
これにより、説明しているような多くのことを実行するための、優れた、スケーラブルで、目に見える、シンプルなメカニズムが提供されました。データベースの基本を理解している場合、それはかなり確実で拡張可能です。アトミック状態遷移トランザクションのため、ロックのロールバックなどの問題は発生していません。
Quartz.NET を使用することをお勧めします
SQL Server、Oracle、MySql、SQLite、Firebirdのプロバイダーがあります。
キューのオフセットを維持するための中間テーブルを作成できます。
create table scan(
scan_id int primary key,
offset_id int
);
複数のスキャンが実行されている可能性があるため、スキャンごとに1つのオフセットがあります。スキャンの開始時にoffset_id = 0を初期化します。
begin;
select * from jobs where order by id where id > (select offset_id from scan where scan_id = 0) asc limit 1;
update scan set offset_id = ? where scan_id = ?; -- whatever i just got
commit;
あなたがする必要があるのは、最後のオフセットを維持することだけです。これにより、スペースも大幅に節約できます(レコードごとのprocess_id)。これが論理的に聞こえることを願っています。