スレッド安全性の問題 - 愚かな操作を覚えておいてください

序文

ヘッドのみが強くなることができます。

テキストは、私のGitHubリポジトリ、歓迎スターに含まれていますgithub.com/ZhongFuChen ...

仕事で愚かな操作を覚え、このキーワード:スレッドセーフ

(どのように私は毎日バグああを書くのです)

一、交代背景

私はここで、そのためのチャネル(例えば、テキストメッセージ、電子メール、マイクロ文字など)あらゆる種類の情報を送信するためにRPCインターフェースを提供するシステムを持っています。次のようにシステムアーキテクチャの私の側には、次のとおりです。

システムアーキテクチャ

要約:サービスシステムは、RPCインターフェイスを提供し、他の人は私が提供するインタフェース、私は判断し、ステッチなどのメッセージサービスシステムのビジネスロジックを呼び出し、内部のメッセージキューにメッセージを配置します。送信側システムの消費メッセージキュー内のデータ、その後、メッセージを送信

例:王は、あなたがメッセージを送信したい、私たちのRPCインターフェイスを呼び出します。私は判断して私の側にパラメータが内部タスクのメッセージキューにスローされます、タスクの定義されたメッセージに組み立てます。送信側システムの消費このタスクは、呼び出しjava.mail完了するために、メールを送信するためにAPI関数を。

王は内側に移動するには、このタスクのメッセージ・キューをスローする限り、サービスシステムとして、私たちのRPCインターフェイスを呼び出して、我々は王への応答を返しました。

  • 限り内部のメッセージキューにタスクとして、我々は成功に戻ります。だから、時には、王は尋ねます:「私は明らかに返していますああ成功で、どのように私はメッセージが行く送信しませんでした」------(非同期

それぞれのメッセージを送信、私たちは情報の保存(MySQLの中に格納されている)、このメッセージになり、MySQLでは、我々はできる、このメッセージを送信し、ステータス送信する時間を知っているようにしています。

そして、王さんもあれば、これらのメッセージは正常に送り出されたかどうかについて非常に懸念している送信は彼の側に再送する必要が失敗しましたそこで彼は、私たちのDBをBINLOG聞き、バイナリログ情報を再送信する必要があるかどうかに応じて判断されます。

様々な理由から、私たちは、あなたが得ることができたときに王RPCインターフェイスを呼び出したい一意のIDを、彼は、このメッセージの成否を判断するように、

  • もちろん、電子メールのIDを倉庫は(彼は私たちにRPCインターフェイスを転送しているため、我々は、タスクがメッセージキューを返します入れます。この場合、送信側システム、それを消費しない)ことはできません

だから、ここでは、ストレージへのすべての方法をサービスシステム内のmessageIdを生成するつもり、その後、彼に戻って、内部のこのmessageIdにタスクに特異的に結合します。

フローチャート

第二に、餌

需要と上記の優れたアイデアを決定した後、ここで私は王は、オブジェクトの応答に戻っ見に行った、とすることを見て、すでにMSGIDを持っているのフィールドを

public class SendResponse {
    
    // 错误码
    private int errCode;

    // 错误信息
    private String errInfo;

    // messageId
    private long msgId;

}
复制代码

私はこの分野での情報のビットを見つけctrl + shift + f、MSGIDはああ使用されていないことがわかりました。私は、これは、私が思いついただけさ、と思いました。私はSendResponseは、列挙クラス、コードのようなものを直接使用することはありませんここで見つけましたが、パンうち、使用状況を見て:

public enum Response {
	
	SUCCESS(1, "success"),
	PARAM_MISSING(2, "param is missing"),
	INVALID_xxxx(3, "xxxx is invalid"),
	INVALID_xxxx(4, "xxxx is invalid"),
	
	private SendResponse sendResponse;
	
	private Response(int errCode, String errInfo) {
		sendResponse = new SendResponse();
		sendResponse.setMsgId(0);
		sendResponse.setErrCode(errCode);
		sendResponse.setErrInfo(errInfo);
	}

	public SendResponse getSendResponse() {
		return sendResponse;
	}

}
复制代码

非常にシンプルな使用する列挙すると、例えば、私は王が渡されたパラメータを持っていることがわかった、私はバックハンドであります:

Response.PARAM_ERROR
复制代码

サービスシステムは、主に2つのことを行います

  • パラメータ/タイプを分析し、様々なビジネスロジックは問題がない、タスクに王パラメータバンドのパッケージには、オーバーオブジェクト
  • 内部のメッセージキューにTaskオブジェクト

二つのタスク

明確にするために:コールは、チェーン全体の終了(メッセージキューにTaskオブジェクト)まで待たなければ、オブジェクトはアウトsendResponseに戻ります。もう少し場所を判断する可能性が高いので、私たちはここにいるので、しかし、設計することで、データを格納するための地図を、全体のリンクを通じて地図

// 首先将sendResponse默认设置为success,也就是代码如下:
map.put("sendResponse",Response.SUCCESS);

// 如果中途某个地方可能有问题了,那我们将Map中sendResponse进行修改
map.put("sendResponse",Response.ERROR);

// 等整条链路完成,从Map拿出sendResponse返回
return map.get("sendResponse");
复制代码

だから私は何をする必要があります:SendResponseリターンの前に、私はユニークなMSGIDを生成し、うまく内部SendResponseオブジェクトに挿入

Response.getSendResponse().setMsgid(uuid);
复制代码

戻る前にsendResponseはMSGIDのように挿入されました

この要件は決定的ラインで、単にそれは間違ってテストしていない、非常に迅速に完了されます。王はその後、需要が供給され、しばらくは問題が何であるかを言わなかっました。

第三に、問題があります

昨日、王は私に言った:「私はメッセージがあなたを失敗した送信するためにここにいる、MSGIDがあり、外観が原因です」

問題のそれ

だから私はラインを記録Qulao、我々は彼が与えMSGIDは、私がメッセージ(ただし、他のタスクのログを)送るようにされていないログの側面を打つことがわかりました。私は、このシステムの問題をパニックだろう、私たちはいますか?

  • 精神活動:一意にこのタスクを識別し、王は私にMSGIDを送ったが、それは他のタスクの内容ですMSGID。それはパニック、(消費者の混乱?データ全体の混乱?)大きな問題ではありません

その後、彼の側には、追加することで行ってきました:

補足情報を続行

メールの発見が正常に送信されたが、彼が得た後に一部を MSGIDは、別のタスクではなく、メッセージです。問題は、メールのみの残りの部分と比較することができているのであれば、なぜのMsgIdを見てください。

主要な問題を解決するために、

第四に、見つける問題

既存の条件は以下のとおりです。

  • メール配信のバッチが成功します
  • 王は、他のタスクのMSGIDを得ました

したがって、判定システムは問題ありませんが、(他のタスクのMSGIDを取得するために)MSGIDの同時時に問題に

だから私は、問題を理由に行ってきました、まだサービスシステムにおけるクラスの元同僚は、チェックコードの時にメモを残しました@NotThreadSafe私は途中で、王につながるが、他のタスクのMSGIDを得たところ、私は気付かなかったことを確認してください感じています。

人間の肉ランチタイムをデバッグしたりアウト見つけることができませんでした:リンク全体に続いて、各スレッドのユニークな操作対象を、オブジェクトのプロパティは、(操作の内部メソッドの両方)をエスケープしていないが、リンクの終わりまで、渡されました。

その後、私は思った、私はMSGIDがそれを生成した場所のようになります。唯一のプロジェクトが内部であることを見つけるために列挙ああ!

// 首先将sendResponse默认设置为success,也就是代码如下:
map.put("sendResponse",Response.SUCCESS);

// 如果中途某个地方可能有问题了,那我们将Map中sendResponse进行修改
map.put("sendResponse",Response.ERROR);

// 把response的msgId的值设置为当前Task绑定的值
map.get("sendResponse").setMsgid(uuid);

// 等整条链路完成,从Map拿出sendResponse返回
return map.get("sendResponse");
复制代码

目覚め

  • 今私は50個のスレッドを持って、データを処理する時には、それぞれ、列挙を識別するために使用されるデフォルトのsendResponseオブジェクトが存在しますResponse.SUCCESSだから、この50件のスレッドがされている共有 sendResponseオブジェクトと
  • 50件のスレッドがsendResponseこの主題を共有し、各スレッドは、当然スレッドセーフされ、内部特性MSGID sendResponseを修正することができます。
  • そこで、王は(あなたがMSGIDを設定した後、3つの曲がったスレッドが曲がっMSGIDで3を得るために王につながる、一度MSGIDを変更しましたが返されませんでした王スレッド)のMSGID他のタスクを取得することができます

要約:

  • 最後に、元同僚は、コード上のmsgidプロパティを保持していたが、このプロパティを使用していない理由を知っています。
  • べきでないバンドを使用して列挙状態属性を持っている(が、変数の属性を変更することができます)

遂に

喜んで出力乾貨物の Javaテクノロジ公共数の:Java3yパブリック数200件の以上のオリジナルの記事技術記事、大量のビデオ資源、美しいマインドマップは、注意が得ることができます!

友人のサークルに転送私にとって最大のサポートです!

私はポイント、記事がよく書かれていると思う賞賛を

おすすめ

転載: juejin.im/post/5d478317f265da03cd0a63ae