典型的なアプリケーションシナリオ飼育係(2)

この記事では、知識から来ている:第VI章「パクシから飼育係にコンセンサスの原則と実践を分散します」

  1. クラスタ管理(子ノード)
  2. マスター選挙(ノードを作成中)
  3. 分散ロック(ノードの作成中)
  4. 分散キュー(ノード作成順)

5.1クラスタ管理

成長の規模分散システムでは、機械規模クラスタも大きくなり、そのため、どのようにクラスタ管理をより良くするかがますます重要になってきています。

いわゆるクラスタ管理含むクラスタモニタクラスタ制御を二つのブロックは、クラスタの実行時状態のコレクションを集束前者、後者は、クラスタを操作して制御されます。日常の運用・保守および開発プロセスでは、私たちはしばしば持って
、次の要件に似ています。

  • 私たちは、仕事で現在のクラスタマシンのどれだけを知りたいです。
  • クラスタの状態データコレクション内の各マシンの動作。
  • 上下のクラスタ機械操作の。

エージェントベースの分散クラスタ管理システムの伝統では、クラスタ内の各マシン上のエージェントによって展開され、中央監視システムにこの取り組みを担うエージェント(指定された制御センターは
、すべてのデータ集中型システムを担っています処理、)一連のレポートを形成し、リアルタイムのアラートを担当して、以下「モニタリングセンター」と呼ばれる報告書は、どこのマシン述べています。適度なシナリオクラスタサイズで、それは本当に
迅速かつ効率的に分散したクラスタ環境の監視を実装するために、生産の練習で広く使われているソリューションですが、システムが動作シナリオは、ソリューションをクラスタサイズが大きいほど増加したら欠点も登場しました:

  • 難易度の大規模なアップグレード:エージェントの形でクライアントの存在は、大規模な使用後は、できるだけ早く状況が出会いの大規模なアップグレードを必要とする、それがコストをアップグレードし、スケジュールをアップグレードコントロールに巨大な課題に直面している、非常に面倒です。
  • 統一されたエージェントは、多様なニーズにお応えすることができません:マシンのCPU使用率、負荷(ロード)、メモリ使用量、ネットワークのスループットとディスク容量や機械の他の基本的な物理的状態のために、統一されたエージェントを使用して
    監視することは満たす必要があります。しかし、あなたは、詳細な内部アプリケーションが必要な場合は、例えば、分散メッセージングミドルウェアでは、各メッセージの消費者支出の状況を監視することが望ましい、いくつかのビジネスの状況を監視し、
    または分散タスクスケジューリングシステムでは、必要性各マシン上のタスクの実施を監視します。明らかに、これらの事業のために結合密接に均一に提供するために、エージェントには適していません要件を監視。
  • プログラミング言語の多様性:より多くのプログラミング言語、異種システムの無限の多様性の出現で。あなたがエージェントの伝統的な方法を使用している場合は、言語Agentクライアントの多様性を提供する必要があります。一方、
    「モニタリングセンターは、」異種データ統合システムにおける大きな課題に直面しています。

ZooKeeperのは、次の2つの特徴があります。

  • ウォッチャー登録クライアントはデータノードのZooKeeperに耳を傾けた場合、データノードまたはその子ノードリストの内容が変更され、その後際、ZooKeeperのサーバは、サブスクライブします
    クライアントが通知を変更送ります。
  • クライアントとサーバ間のセッションが失敗した後のZooKeeper上で作成した一時的なノードは、その後、一時的なノードは自動的にクリアされます。

これらの二つの特性のZooKeeperの使用は、システムは、別のクラスタマシンライブネス監視を実現することができます。例えば、監視システムは/clusterServers、ノード監視モニターに登録
になり、マシンが追加動的に操作され、その後いつでも/clusterServersノードの下の一時的なノードを作成します/clusterServers/[Hostname]できるリアルタイム監視システムとして
のフォローアップ治療として、機械の状況の変化を検出するシステムの運用を監視することです。ここでは、分散ログ収集管理システムとのZooKeeperを使用する方法のこれらの2つの典型的な例は、ホスティング・オンラインクラウド経由見て取り
、クラスタ管理を実装します。

5.1.1分散ログ収集システム

分散ログ収集システムのコアの仕事は、私たちが、分散ログ収集モジュールシステムに焦点を当ててどこを見て、システムログを収集するために別のマシンに分散されます。

システムログの典型的なアーキテクチャでは、すべてのシステムのログは、それぞれコレクタに対応し、複数のグループに収集することができるため、マシン全体(例えばマシンの以下「ログソースマシン」)をログに記録します、
コレクタは、実際にログを収集するためのバックグラウンド・マシン(例えばマシンの以下「コレクタ機」)です。大規模分散ログ収集システムのシーンのために、通常我々は、次の2つの質問を解決する必要があります。

  • ソースマシンの変更のログ:本番環境では、マシンの変化に伴って、ほぼすべてのマシンの変更内の各アプリケーション(マシンのハードウェアの問題、拡張、またはネットワークルームの移行の問題は、変更マシンのアプリケーションにつながる)毎日、
    それは、ログソースマシンの各グループは、通常は常に変化していると言うことです。
  • 変更コレクターマシン:収集システム自体は変更したり、マシンの拡張する必要がありますログインするので、新しいコレクタが追加されたり、古いコレクタマシンの出口が表示されます場合。

それは、ソースログ収集マシンまたはマシンの変更があるか否かを、上記2つの問題は、最終的に一つに集約:どのように迅速かつ合理的に、動的に全体となっている各コレクタに対応するログソースマシンに割り当てる
適切ロギングシステムを安定した動作の前提は、ログ収集プロセスは最大の技術的な課題です。この場合、ZooKeeperのの導入はのは、こののZooKeeperを見てみましょう、良い選択である
利用シーン。

0.1登録されたコレクタマシン

ZooKeeperのログ収集システムを登録するために使用される、典型的なアプローチは、例えば、ZooKeeperのコレクタにルートノードとしてノードを作成することである/logs/collector(以下、我々 「コレクタ
、ノードは」データノードを表す)各コレクタ機開始彼らは例えば、コレクタノードで独自のノードを作成することlogs/collector/[Hostname]

0.2タスク分散

マシンが自ノードに対応して作成されているすべてのコレクタ後、下部サブコレクタノードの数に応じて、システムのノードは、すべてのログソースマシンに対応するグループに分割され、その後、機械パケットが後にリストに書き込まれ
、コレクタマシン(たとえば、作成された子ノードが/logs/collector/host1)上がります。このように、各コレクタログソースマシン対応コレクタノードからの独自のリストを取得することが可能な機械、
およびログ収集が開始され、さらに。

0.3ステータスレポート

登録やコレクターマシンタスクの配布が完了すると、我々は可能性はいつでもこれらのマシンのハングを考慮に入れる必要があります。:したがって、この問題のために、我々は状況コレクターを報告するためのメカニズムを持っている必要があり
、各コレクタのマシンを、独自の排他的なノードを作成した後、また、あなたが例えば、対応する子ノードの子ノードの状態を作成する必要があり/logs/collector/host1/status、各コレクションをこれは、定期的にする必要がある
ノードに自身のステータス情報を書き込みます。私たちは通常、コレクタマシンは、このノードでのログ収集進捗情報に書き込まれます、検出メカニズムとして見られ、この戦略を置くことができます。子ノードの状態の最後の更新のロギングシステム時間は
、対応するコレクタ生存マシンかどうかを決定します。

0.4動的割り振り

コレクタマシンがハングアップまたは拡張した場合、我々は動的に収集タスクを割り当てる必要があります。動作中、システムは常にログに注目し/logs/collector、変更このノードのすべての子ノードを
コレクターマシンの検出時には、タスクの再割り当てを開始します、レポート機能を停止したり参加する新しいコレクタマシンを持っています。それは、コレクターのために停止しているかどうか、マシンを報告したり、新しいマシンが追加
のログシステムは、すべてのタスクの転送コレクタの前に割り当てる必要があります。この問題を解決するために、通常は二つのアプローチがあります。

.4.1グローバルダイナミックアロケーション

これは単純な、粗アプローチである、ログシステムによれば、コレクタまたはマシンが追加された新しいマシンの場合にハングアップすると、新しいマシンにコレクタのリストを必要とする、直ちに、すべてのログソースマシンの再グルーピングを行い
、次いでコレクタマシンの残りの部分に割り当てられています。

.4.2ローカルダイナミックアロケーション

グローバルダイナミックアロケーション戦略はシンプルですが、問題はありますが:機械または部品のコレクタを変更して、タスクのグローバル動的割り当てをリードする、影響が比較的大きかったので、リスクも大きくなります。
定義によっていわゆるローカル動的割り当ては、小さな範囲内のタスクの動的割り当てです。この戦略では、同時に各コレクタマシンは、独自のログ収集の状況を報告するために、それは自身の負荷アップを報告します。
ここに記載の負荷だけではなく、単にマシンのCPU負荷(ロード)が、タスクのための集電体の総合的な評価であることに注意してください。

コレクタ・マシンがハングアップした場合、この戦略では、システムは、それらの低負荷機械アップに再割り当て前のマシンに割り当てられたタスクを記録します。新しいコレクターのマシンを追加した場合も同様に、
これらの高負荷のマシンから新しいマシンを追加する作業のこの部分に転送されます。

0.5ノート

.5.1ノードタイプ

まず見/logs/collectorノードタイプのノードの子ノード。子ノード以下のすべて、このノードは、各コレクタ・マシン、これらの子ノードは、一時的なノードを選択しなければならないこと、その後予備的見解を表す
ロギングシステムは、これらの一時ノードに基づくコレクタマシンの生存率を測定することができるために。しかし、それはまた、これであるために注意を払う必要があります。このシナリオでは、分散ログ収集は、すべてのコレクタノードの保持する
だけで、独自の一時的なZooKeeperのノードに依存している場合は、ログソースのマシンのリストは、マシンのコレクタに割り当てられていますコレクタまたはコレクタのマシンブレイク「ハートビートレポート」ときのメカニズムは、その後、ハングアップ
セッションの時間が経過した後は、コレクタノードの障害であることをZooKeeperのはすぐにノードを削除し、そのノードに記録ログソースのリストとともに、すべてのマシンが出てクリアされます。

以上の説明から、このビジネスの需要を満たすことが明確にできない、一時的なノードを知っているので、我々は、各コレクタのマシンは、以下の永続的なノードにそれぞれ作成し、永続的なノード識別子を使用して選択することができる
/logs/collector/[Hostname]/status各コレクタマシンを特徴付けるためにノード状態。結果として、両方のは、すべてのシステムのログ収集のモニタリングを達成するが、コレクタ・マシンがハング
正確還元たタスクを割り当てる後、まだできます。

.5.2ログ・システム・ノード・モニター

実際の生産工程において、ノードの自身の状態の周波数を変更するために、各コレクタ・マシンは、(秒以下ごとに一度のように)非常に高いとすることができる、及びロギングシステムは、すべての監視場合コレクタの数は、非常に大きくなることが
これらのノードの変更を、その後、メッセージ通知の量が非常に大きくなることができます。一方、コレクタ機の場合にはログインし、適切に動作システムは、各時間リアルタイムでノードのステータス変更を受信する必要がないので、ほとんどの
これらの変更の通知を無駄にしています。したがって、我々はこのようにLANトラフィックの多くを保存し、モニターの設定をあきらめ、代わりのロギングシステムの政策イニシアチブポーリングコレクタノードを使用して検討し、唯一の欠点があるということです
口座に分散ログ収集システムのポジショニングを取って一定の遅延が(。この遅延)が許容されます。

5.1.2オンラインクラウドホスティング管理

オンラインクラウドホスティング管理シナリオは、通常、これらのウェブホスティングプロバイダで発生します。クラスタ管理のこのタイプでは、クラスタマシンの監視の非常に重要な部分があります。通常、クラスタ内のステート・マシンのためのこのシーン
は特に統計オンライン率が高いの要件を持っているマシン、および迅速クラスタマシンの変化に対応する必要があります。

伝統的な実装では、監視システムへの独自の定期的な報告(ホストポートを指定した検出など)いくつかの手段によって、各マシンのタイミング、または各マシンを検出するためのシステムを監視し、「私は生きているんです。」
しかし、このアプローチは、その上のネットワーク通信プロトコルの設計、スケジューリングおよび災害復旧との多くの些細な問題に対処するために、各開発者は、独自のビジネスシステムが必要です。のは、別のクラスタマシンのZooKeeperが達成使用して見てみましょう
生存率監視システムを。このシステムでは、我々は次のようにポイントは一般的に必要とします。

  • どのように迅速現在の運用環境の統計どのように多くのマシンの合計?
  • どのようにすばやくマシンケース/オフラインに取得するには?
  • クラスター状態で各ホストをリアルタイム監視を実行するには?

/オフラインで0.1マシン

操作やラインのメンテナンスを自動化するために、我々はマシン上/ラインの下のグローバルモニタリングがある必要があります。通常時に新しいマシン、あなたが行くこれらのマシンに展開エージェントを指定する必要があります。
エージェントの配備が開始された後、それは最初に指定されたノードのZooKeeperに登録され、具体的なアプローチは、例えば、以下のノードマシンリストに一時的な子ノードを作成することで/XAE/machine/[Hostname]
、以下に示すように、(このノードの以下「マスターノード」):


エージェントは、ZooKeeperの上、この一時的な子ノードを作成したときに/XAE/machines監視センタノードの注目は「子ノードの変更」イベントを受信しているであろう、それは、オンラインの通知であるので、彼はこの上に置くことができ
、オープン対応する背景管理ロジックが新しく追加されたマシン。簡単に取得することがありながら一方で、監視センターはまた、組立ラインオフマシンを気づくために得ることができ、これは、オン/オフ組立ラインへのマシンの検出を達成する
大規模な拡大と能力の評価のために、オンラインマシンのリストに大きな助け。

 

0.2機械監視

オンラインクラウドホストシステムについては、オンラインマシンの状態を検出するだけでなく、あなたはまた、機械の状態監視を実行する必要があります。動作の過程で、ホストのエージェントタイミング動作状態情報
のZooKeeper、これらのノード変更通知データをサブスクライブすることによって間接的に情報を取得するために実行時間監視センタホストの書き込みホストノード。

分散システムはますます大規模になるように、クラスタ・マシンの監視と管理がますます重要になります。ZooKeeperのによって実装この方法は、上記では、唯一のクラスタをリアルタイムで検出することができる
機械の場合にはオン/オフ組立ラインではなく、リアルタイムでホスト情報の動作中に取得することができ、大規模なクラスタを構築することが可能ですホストプロファイル。

6.1マスター選択

マスターの選挙は、分散システムでは非常に一般的なシナリオです。システムユニットは、独立したコンピューティングパワーは、完全構成する、異なるマシン上に展開することができる分散型コア特性である
分散システム。同時に、実際のシーンは、多くの場合、スタンドアローンのシステムユニットを、私たちは「マスター」と呼ぶコンピュータサイエンス、異なるマシンに分散これらの中には、いわゆる「ボス」を選択する必要があります。

分散システムでは、多くの場合、他のクラスタ・システムのユニットを調整するために使用されるマスターは、分散システムの状態の変化を決定する権利を有します。例えば、いくつかのアプリケーションでは、別の読み取りとシナリオを書き、クライアントは通常、書き込み要求がされた
マスターによって処理され、他の人にいくつかの複雑なロジック、および処理結果のために他のシナリオでは、マスターは、多くの場合、責任をクラスタ同期にシステムユニット。マスターの選挙は、ZooKeeperの言うことができる
最も一般的なアプリケーションのシナリオは、このセクションでは、我々はのZooKeeperクラスタマスター選挙でシナリオを見て、「大量のデータ処理と共有モデル」に、この特定のケースを兼ね備えています。

分散環境では、多くの場合、このシナリオに直面している:このような製品のID、またはウェブサイトのカルーセル広告IDなどのフロントエンドサービスに必要なデータを、提供するために、クラスタ単位のすべてのシステム(通常は
いくつかの広告に登場システム)、これは、多くの場合、非常に時間がかかる/ O処理およびI CPUリソースである---- ID番号は、多くの場合、大量のデータ処理を計算する必要があるから得られる商品又は広告IDである、など。
クラスタ内のすべてのマシンが、この計算ロジックを実行する場合、計算プロセスの複雑さを考えると、それは多くのリソースの費用がかかります。より良いアプローチは、クラスタの一部を許可する、あるいは単にすることで
どれが大幅にでき、それらをデータ・コンピューティング、一度計算結果は、クラスタ全体のすべての他のクライアントマシンに共有することができるデータを処理するための機械に行きましょうパフォーマンスを向上させる、作業の重複を減らします。

ここでは、この広告モデルを説明するために、簡単な例を用いて、システムのバックグラウンドシーンを入れました。全体のシステムは、一般に、クライアント・クラスタ、分散キャッシュシステム、大量のデータ処理バスとのZooKeeperに分けることができ
、以下に示すように、四つの部分:


クライアントクラスタのタイミングは、ZooKeeperのマスター選挙日によって達成されます。クライアント選出されたマスターした後、このマスターは、大量のデータの一連の処理のために責任を負うこと、そして最終的に計算されます
結果を、データおよびメモリ/データベースに配置します。一方、マスターはまた、すべてのクライアントがこのメモリ/データベースからの結果を共有し、クラスタ内の他のユーザーに通知する必要があります。

 

次に、我々はマスター選挙に基づく要件を明確にするためにまず、選挙プロセスのマスターの焦点になります:マシンの選挙を、クラスタ内のすべてのマシンのマスターとして。この需要に対処するために、通常は
次の、我々が達成するための重要な特徴を共通リレーショナルデータベースを選択することができます:クラスタ挿入内のすべてのマシンがデータベースに同じ主キーIDを記録し、データベースは、私たちは、主キーの競合の自動化に役立つ
、チェックをそれは、成功するためにすべての顧客の挿入端機、1台のマシンのみである----そして、我々は、顧客データベースが正常にデータの端末機がマスターとなっ挿入するようにと考えています。

一見すると、このプログラムはうまく唯一のマスターに選出されたクラスタに保証することができ、主キーの特性リレーショナルデータベースに頼って、作業を行います。しかし、我々は考慮する必要があるもう一つの問題は、現在の場合ということで
選出されたマスターがハングアップし、その後、どのように対処しますか?たむろするために私に言ったマスター?明らかに、リレーショナルデータベースは、このイベントをお知らせすることはできません。

ZooKeeper的强一致性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即ZooKeeper将会保证客户端无法重复创建一个已经存在
的数据节点。也就是说,如果同时有多个客户端请求创建同一个节点,那么最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很容易地在分布式
环境中进行Master选举了。

在这个系统中,首先会在ZooKeeper上创建一个日期节点,如下图:


客户端集群每天都会定时往ZooKeeper上创建一个临时节点,例如/master_election/2017-09-03/binding。在这个过程中,只有一个客户端能够成功
创建这个节点,那么这个客户端所在机器就称为了Master。同时,其他没有在ZooKeeper上成功创建节点的客户端,都会在节点/master_ecection/2017-09-03
上注册一个子节点变更的Watcher,用于监控当前的Master机器是否存活,一旦发现当前的Master挂了,那么其余的客户端将会重新进行Master选举。

 

从上面的讲解中,我们可以看到,如果仅仅只是想实现Master选举的话,那么其实只需要有一个能够保证唯一性的组件即可,例如关系型数据库的主键模型
就是不错的选择。但是,如果希望能够快速地进行集群Master动态选举,那么基于ZooKeeper来实现是一个不错的新思路。

7.1 分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的
时候,往往需要通过一些互斥手段来防止彼此之间的干扰,以保证一致性,在这种情况下,就需要使用分布式锁了。

在平时的实际项目开发中,我们往往很少会去在意分布式锁,而是依赖于关系型数据库固有的排他性来实现不同进程之间的互斥。这确实是一种非常简便且被
广泛使用的分布式锁实现方式。然而有一个不争的事实是,目前绝大多数大型分布式系统的性能瓶颈都集中在数据库操作上。因此,如果上层业务再给数据库
添加一些额外的锁,例如行锁、表锁甚至是繁重的事务处理,那么是不是会让数据库更加不堪重负呢?下面我们来看看使用ZooKeeper如何实现分布式锁,
这里主要讲解排他锁和共享锁两类分布式锁。

7.1.1 排他锁

排他锁(Exclusive Locks,简称X锁),又称为写锁或独占锁,是一种基本的锁类型。如果事务T1对数据对象O1加上了排他锁,那么在整个加锁期间,只允许
事务T1对O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作————直到T1释放了排他锁。

从上面讲解的排他锁的基本概念中,我们可以看到,排他锁的核心是如何保证当前有且仅有一个事务获得锁,并且锁被释放后,所有正在等待获取锁的事务都
能够被通知到。下面我们就看看如何借助ZooKeeper实现排他锁。

.1 定义锁

有两种常见的方式可以用来定义锁,分别是synchronized机制和JDK5提供的ReentrantLock。然而,在ZooKeeper中,没有类似于这样的API可以直接使用,
而是通过ZooKeeper上的数据节点来表示一个锁,例如/exclusive_lock/lock节点就可以被定义为一个锁,如下图:

 

.2 获取锁

在需要获取排他锁时,所有的客户端都会试图通过调用create()接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。而ZooKeeper
会保证在所有的客户端中,最终只有一个客户端能够创建成功,那么就可以认为该客户端获取了锁。同时,所有没有获取到锁的客户端就需要到/exclusive_lock
节点上注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况。

.3 释放锁

由于是临时节点,有下面两种情况,可能释放锁:

  • 当前获取锁的客户端机器发生宕机
  • 正常执行完业务逻辑后,客户端主动将临时节点删除。

无论在上面情况下移除了lock节点,ZooKeeper都会通知所有在/exclusive_lock节点上注册了子节点变更Watcher监听的客户端。这些客户端在接收到通知后,
再次重新发起分布式锁获取,即重复“获取锁”过程。如下图:

 

7.1.2 共享锁

共享锁(Shared Locks,简称S锁),又称读锁,同样是一种基本的锁类型。如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,
其他事务也只能对这个数据对象加共享锁————直到该数据对象上的所有共享锁都被释放。

共享锁和排他锁最根本的区别在于,加上排他锁后,数据对象只对一个事务可见,而加上共享锁后,数据对所有事务都可见。

.1 定义锁

和排他锁一样,同样是通过ZooKeeper上的数据节点来表示一个锁,是一个类似于/shared_lock/[Hostname]-请求类型-序号的临时顺序节点,例如
/shared_lock/192.168.0.1-R-0000000001,那么,这个节点就代表了一个共享锁,如下图:

 

.2 获取锁

在需要获取共享锁时,所有客户端都会到/shared_lock这个节点下面创建一个临时顺序节点,如果当前是读请求,那么就创建例如/shared_lock/192.168.0.1-R-000000001/
的节点;如果是写请求,那么就创建例如/shared_lock/192.168.0.1-W-000000001的节点。

.3 判断读写顺序

根据共享锁的定义,不同的事务都可以同时对同一数据对象进行读取操作,而更新操作必须在当前没有任何事务进行读写操作的情况下进行。基于这个原则,
我们来看看如何通过ZooKeeper的节点来确定分布式读写顺序,大致可以分为如下4个步骤。

  1. 创建完节点后,获取/shared_lock节点下的所有子节点,并对该节点注册子节点变更的Watcher监听。
  2. 确定自己的节点序号在所有子节点中的顺序。
  3. 如果当前节点业务为读请求:如果没有比自己序号小的子节点,或是所有比自己序号小的子节点都是读请求,那么表明自己已经成功获取到了共享锁,同时
    开始执行读取逻辑。如果比自己序号小的子节点有写请求,那么就需要进入等待。
    如果当前节点业务为写请求:如果自己不是序号最小的子节点, 那么就需要进入等待。
  4. 接收到Watcher通知后,重复步骤1。

.4 释放锁

释放锁的逻辑和排他锁是一致的。

.5 羊群效应

上面讲解的这个共享锁实现,大体上能够满足一般的分布式集群竞争锁的需求,并且性能都还可以————这里说的一般场景是指集群规模不是特别大,一般是在
10台机器以内。但是如果机器规模扩大之后,会有什么问题呢?我们着重来看上面“判断读写顺序”过程的步骤3,如下图,看看实际运行中的情况。

 

  1. 192.168.0.1这台机器首先进行读操作,完成读操作后将节点/192.168.0.1-R-000000001删除。
  2. 余下的4台机器均收到了这个节点被移除的通知,然后重新从/shared_lock/节点上获取一份新的子节点列表。
  3. 每个机器判断自己的读写顺序。其中192.168.0.2这台机器检测到自己已经是序号最小的机器了,于是开始进行写操作,而余下的其他机器发现没有轮到
    自己进行读取或更新操作,于是继续等待。
  4. 继续......

上面这个过程就是共享锁在实际运行中最主要的步骤了,我们着重看下上面步骤3中提到的:“而余下的其他机器发现没有轮到自己进行读取或更新操作,于是继续等待。”
很明显,我们看到,192.168.0.1这个客户端在移除自己的共享锁后,ZooKeeper发送了子节点变更Watcher通知给所有机器,然而这个通知除了给192.168.0.2
这台机器产生实际影响外,对于余下的其他所有机器都没有任何作用。

相信读者也已经意思到了,在这整个分布式锁的竞争过程中,大量的“Watcher通知”和“子节点列表获取”两个操作重复运行,并且绝大多数的运行结果都是
判断出自己并非是序号最小的节点,从而继续等待下一次通知————这个看起来显然不怎么科学。客户端无端地接收到过多和自己并不相关的事件通知,如果在集群
规模比较大的情况下,不仅会对ZooKeeper服务器造成巨大的性能影响和网络冲击,更为严重的是,如果同一时间有多个节点对应的客户端完成事务或是事务
中断引起节点消息,ZooKeeper服务器就会在短时间内向其余客户端发送大量的事件通知————这就是所谓的羊群效应。

上面这个ZooKeeper分布式共享锁实现中出现羊群效应的根源在于,没有找准客户端真正的关注点。我们再来回顾一下上面的分布式锁竞争过程,它和核心
逻辑在于:判断自己是否是所有子节点中序号最小的。于是,很容易可以联想到,每个节点对应的客户端只需要关注比自己序号小的那个相关节点的变更情况
就可以了————而不需要关注全局的子列表变更情况。

.6 改进后的分布式锁实现

现在我们来看看如何改进上面的分布式锁实现。首先,我们需要肯定的一点是,上面提到的共享锁实现,从整体思路上来说完全正确。这里主要的改动在于:
每个锁竞争者,只需要关注/shared_lock/节点下序号比自己小的那个节点是否存在即可,具体实现如下:

  1. 客户端调用create()方法创建一个类似于/shared_lock/[Hostname]-请求类型-序号的临时顺序节点。
  2. 客户端调用getChildren()接口来获取所有已经创建的子节点列表,注意,这里不注册任何Watcher。
  3. 如果无法获取共享锁,那么就调用exist()来对比自己小的那个节点注册Watcher。注意,这里“比自己小的节点”只是一个笼统的说法,具体对于读请求和写请求不一样。
    读请求:向比自己序号小的最后一个写请求节点注册Watcher监听。
    写请求:向比自己序号小的最后一个节点注册Watcher监听。
  4. 等待Watcher通知,继续进入步骤2。

流程图如下:

 

.7 注意

看到这里,相信很多读者都会觉得改进后的分布式锁实现相对来说比较麻烦。确实如此,如同在多线程并发编程实践中,我们会去尽量缩小锁的范围————对于
分布式锁实现的改进其实也是同样的思路。那么对于开发人员来说,是否必须按照改进后的思路来设计实现自己的分布式锁呢?答案是否定的。在具体的实际开发
过程中,我们提倡根据具体的业务场景和集群规模来选择适合自己的分布式锁实现:在集群规模不大、网络资源丰富的情况下,第一种分布式锁实现方式是
简单实用的选择;而如果集群规模达到一定程度,并且希望能够精细化地控制分布式锁机制,那么不妨试试改进版的分布式锁实现。

8.1 分布式队列

业界有不少分布式队列产品,不过绝大多数都是类似于ActiveMQ、Kafka等的消息中间件。在本节中,我们主要介绍基于ZooKeeper实现的分布式队列。
分布式队列,简单地讲分为两大类,一种是常规的先入先出队列,另一种则是要等到队列元素集聚之后才统一安排执行的Barrier模型。

8.1.1 FIFO:先进先出

使用ZooKeeper实现FIFO队列,和共享锁的实现非常类似。FIFO队列就类似于一个全写的共享锁模型,大体的设计思想其实非常简单:所有客户端都会到
/queue_fifo这个节点下面创建一个临时顺序节点,例如/queue_fifo/192.168.0.1-0000000001,如下图:


创建完节点之后,根据如下4个步骤来确定执行顺序。

 

  1. 通过调用getChildren()接口来获取/queue_fifo节点下的所有子节点,即获取队列中所有的元素。
  2. 确定自己的节点序号在所有子节点中的顺序。
  3. 如果自己不是序号最小的子节点,那么就需要进入等待,同时向比自己序号小的最后一个节点注册Watcher监听。
  4. 接收到Watcher通知到,重复步骤1。

整个FIFO队列的工作流程,如下图:

 

8.1.2 Barrier:分布式屏障

Barrier原意是指障碍物、屏障,而在分布式系统中,特指系统之间的一个协调条件,规定了一个队列的元素必须都集聚后才能统一进行安排,否则一直等待。
这往往出现在那些大规模分布式并行计算的应用场景了:最终的合并计算需要基于很多并行计算的子结果来进行。这些队列其实是FIFO队列的基础上进行了
增强,大致的设计思想如下:开始时,/queue_barrier节点是一个已经存在的默认节点,并且将其节点的数据内容赋值为一个数字n来代表Barrier值,
例如n=10表示只有当/queue_barrier节点下的子节点个数达到10后,才会打开Barrier。之后,所有的客户端都会到/queue_barrier节点下创建一个
临时节点,例如/queue_barrier/192.168.0.1,如下图:


创建完节点之后,根据如下5个步骤来确定执行顺序。

 

  1. 通过调用getDate()接口获取/queue_barrier节点的数据内容:10。
  2. 通过调用getChildren()接口获取/queue_barrier节点下的所有子节点,即获取队列中所有元素,同时注册对子节点列表变更的Watcher监听。
  3. 统计子节点的个数。
  4. 如果子节点个数还不足10个,那么就需要进入等待。
  5. 接收到Watcher通知后,重复步骤2。


    博主理解为,如果在很少的时间内,同时超过了10个以上的业务机创建了临时节点,那么业务处理的速度并不是恒定的,因为有可能这个业务被11个机器处理,
    下一个被12个业务机处理?



作者:李文文丶
链接:https://www.jianshu.com/p/bd01abf2eaae
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

おすすめ

転載: blog.csdn.net/demon7552003/article/details/92055789