スレッドを着色するためのアプローチ

会社の私たちは常に、マルチスレッドの問題を含む、一般に受け入れられている慣行を使用して、コードの保守性を高めるよう努めています。 これは、増え続ける負荷がもたらすすべての困難を解決するわけではありませんが、サポートを簡素化します。また、コードの可読性と新機能の開発速度を獲得します。



現在、1日あたり47,000人のユーザー、約30台のサーバー、1秒あたり2,000個のAPIリクエスト、および毎日のリリースがあります。 Miroサービスは2011年から開発されており、現在の実装では、ユーザー要求は異種サーバーのクラスターによって並列に処理されます。







競合アクセス管理サブシステム



当社の製品の主な価値は、共同ユーザーボードであるため、主な負担はユーザーボードにあります。 競合するアクセスのほとんどを制御するメインサブシステムは、ボード上のユーザーセッションのステートフルシステムです。



いずれかのサーバー上のオープン可能なボードごとに、状態が上昇します。 コラボレーションとコンテンツの表示を保証するために必要なアプリケーションランタイムデータと、処理スレッドへのバインドなどのシステムデータの両方を保存します。 状態が保存されているサーバーに関する情報は、分散構造に書き込まれ、サーバーが実行されていて、少なくとも1人のユーザーがボードにいる限り、クラスターからアクセスできます。 Hazelcastを使用して、サブシステムのこの部分を提供します。 ボードへの新しい接続はすべて、この状態でサーバーに送信されます。



サーバーに接続するとき、ユーザーは受信ストリームを入力します。受信ストリームの唯一のタスクは、接続を対応するボードの状態にバインドすることです。



ボードには、ネットワーク、処理接続、およびビジネスロジックを担当する「ビジネス」という2つのストリームが関連付けられています。 これにより、ネットワークパケットを処理し、ビジネスコマンドを実行する異種タスクの実行をシリアルからパラレルに変換できます。 ユーザーからの処理されたネットワークコマンドは、適用されたビジネスタスクを形成し、それらをビジネスストリームに導き、そこで順次処理されます。 これにより、アプリケーションコードの開発時に不要な同期が回避されます。



コードをビジネス/アプリケーションおよびシステムに分割することは、社内規約です。 これにより、サービスツールである通信、スケジューリング、およびストレージの低レベルの詳細から、ユーザーの機能に対応するコードを区別できます。



ボードに状態がないことを受信ストリームが検出すると、対応する初期化タスクが設定されます。 状態の初期化は、別のタイプのスレッドによって処理されます。



タスクのタイプとその方向は、次のように表すことができます。







このような実装により、次の問題を解決できます。



  1. 受信ストリームには、新しい接続を遅くする可能性のあるビジネスロジックはありません。 サーバー上のこのタイプのストリームは単一のコピーに存在するため、その遅延はボードのオープン時間に直ちに影響し、ビジネスコードにエラーがある場合、簡単にハングさせることができます。
  2. 状態の初期化は、ボードのビジネスフローでは実行されず、ユーザーからのビジネスコマンドの処理時間には影響しません。 時間がかかる場合があり、ビジネスフローは複数のボードを一度に処理するため、新しいボードを開いても既存のボードに直接影響しません。
  3. ネットワークコマンドの解析は、コマンドを直接実行するよりも高速であることが多いため、システムリソースを効率的に使用するために、ネットワークスレッドプールの構成はビジネススレッドプールの構成とは異なる場合があります。


スレッドカラーリング



上記のサブシステムは、実装において非常に重要です。 開発者は、システムのスキームを念頭に置いて、ボードを閉じる逆のプロセスを考慮する必要があります。 閉じるとき、すべてのサブスクリプションを削除し、レジストリからエントリを削除し、初期化された同じストリームでこれを実行する必要があります。



このサブシステムで発生したバグとコード変更の複雑さは、多くの場合、実行コンテキストの理解不足に関連していることに気付きました。 スレッドとタスクをジャグリングすると、特定のコードがどの特定のスレッドで実行されているのかという質問に答えるのが難しくなりました。



この問題を解決するために、スレッドに色を付ける方法を使用しました。これは、システムでのスレッドの使用を規制することを目的としたポリシーです。 色はスレッドに割り当てられ、メソッドはスレッド内で実行するスコープを決定します。 ここでの色は抽象化であり、列挙などの任意のエンティティにすることができます。 Javaでは、注釈はカラーマーキング言語として機能できます。



@Color @IncompatibleColors @AnyColor @Grant @Revoke
      
      





メソッドに注釈が追加され、その助けを借りてメソッドの有効性を設定できます。 たとえば、メソッドの注釈で黄色と赤が許可されている場合、最初のスレッドがメソッドを呼び出すことができ、2番目のスレッドではそのような呼び出しがエラーになります。







無効な色を指定できます:







ダイナミクスでスレッド特権を追加および削除できます。







以下の例のように注釈または注釈がない場合、メソッドは任意のスレッドで実行できます。







Android開発者は、注釈MainThread、UiThread、WorkerThreadなどのこのアプローチに精通している可能性があります。



スレッドの色付けは自己文書化コードの原則を使用し、メソッド自体は静的分析に適しています。 静的分析を使用すると、コードが実行される前に、コードが正しく記述されているかどうかを判断できます。 GrantアノテーションとRevokeアノテーションを除外し、初期化時のストリームに既に変更できない特権セットがあると仮定すると、これはフローに依存しない分析-呼び出しの順序を考慮しない静的分析の単純なバージョンになります。



フローカラーリングメソッドの実装時には、devopsインフラストラクチャには静的解析用の既製のソリューションがなかったため、よりシンプルで安価な方法で進めました。各タイプのフローに一意に関連付けられた注釈を導入しました。 実行時のアスペクトの助けを借りて、それらの正確性をチェックし始めました。



 @Aspect public class ThreadAnnotationAspect { @Pointcut("if()") public static boolean isActive() { … //   ,    . , ,    } @Pointcut("execution(@ThreadAnnotation * *.*(..))") public static void annotatedMethod() { } @Around("isActive() && annotatedMethod()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Thread thread = Thread.currentThread(); Method method = ((MethodSignature) jp.getSignature()).getMethod(); ThreadAnnotation annotation = getThreadAnnotation(method); if (!annotationMatches(annotation, thread)) { throw new ThreadAnnotationMismatchException(method, thread); } return jp.proceed(); } }
      
      





アスペクトについては、Aspectjライブラリと、プロジェクトのコンパイル時にウィービングを提供するmavenプラグインを使用します。 Weavingは、ClassLoaderを使用してクラスをロードするときに、ロード時に初期設定されていました。 しかし、同じクラスを競合的にロードすると、weaverが誤った動作をすることがあり、その結果、クラスのソースコードは変更されないという事実に直面しました。 その結果、これは非常に予測不可能で再現が困難な本番動作をもたらしました。 おそらく、ライブラリの現在のバージョンでは、そのような問題はありません。



アスペクトのソリューションにより、コード内のほとんどの問題をすばやく見つけることができました。



注釈を常に最新の状態に保つことを忘れないことが重要です:注釈を削除したり、遅延を追加したり、織り面を完全にオフにしたりできます。この場合、色付けはその関連性と価値をすぐに失います。



守衛



色付けの種類の1つは、java.util.concurrentのGuardedBy注釈です。 フィールドとメソッドへのアクセスを制限し、正しいアクセスに必要なロックを示します。



 public class PrivateLock { private final Object lock = Object(); @GuardedBy (“lock”) Widget widget; void method() { synchronized (lock) { //Access or modify the state of widget } } }
      
      





最新のIDEは、この注釈の分析もサポートしています。 たとえば、コードに問題がある場合、IDEAは次のメッセージを表示します。









スレッドのカラーリングの方法は新しいものではありませんが、マルチスレッドアクセスが変更可能なオブジェクトに頻繁にアクセスするJavaのような言語では、ドキュメントのフレームワークだけでなく、コンパイル段階でも使用することで、アセンブリがマルチスレッドコードの開発を大幅に簡素化できるようです。



アスペクトの実装は引き続き使用します。 システム変更に対するこのアプローチの安定性を高めることができる、より洗練されたソリューションまたは分析ツールに精通している場合は、コメントで共有してください。



All Articles