私はネッティーでZkClientをロールアップする方法あなたを教え

序文

その理由は、このアイデアaは、登録情報のダボを取得するように、直接jsの飼育係の使用に接続することはできません試してみたい、前気まぐれながらあります。

その後、いくつかの検索情報の後に、私たちはとても純粋なJSが達成ますが、いくつかの偉大な神が、使用nodeJsはZKクライアントを達成見つけることができません。この成功は私の興味を喚起し、純粋なJSとしてTCPソケット通信をサポートしていないことがわかった。シンプルな少し研究ZK通信プロトコルの後、私は手のラインを試すようになった、そしてもちろんのZKクライアントはJavaで実装されています

考え

飼育係通信プロトコルは、ヘッダ、コンテンツのバイト数を指定する典型的な「ヘッダ/内容」構造であり、コンテンツは、特定のメッセージデータです。

それは、ヘッダ/コンテンツ構造があるので、それは、エンコード、デコードおよびネッティーこの偉大なアーティファクトで使用LengthFieldBasedFrameDecoderにLengthFieldPrependerのネッティーを使用して考えるのは簡単です、少ないリソースでより多くの何かを行うことができます。これは、使用ネッティーに決定しました開発。

クライアントの選択決定は、ビューのプロトコルの観点から、ZKの異なるバージョン間であまりにも多くの互換性の問題があってはならない。我々は、サーバー側のデバッグを持っている必要があります、終了し、その差は確かにありました。だから、あなたの便宜のために、我々はここで、サーバーの飼育係のバージョンが3.4.12である上位バージョンや厳格な互換性テストを行っていない低いバージョンを定義します。

注:バージョンに加えて、クライアントのみ、スタンドアロンモードでテスト、およびクラスタモードで検証されませんが、[彼の後ろに]を介して実行される作業を簡素化します。

準備

	stat path [watch]
	set path data [version]
	ls path [watch]
	delquota [-n|-b] path
	ls2 path [watch]
	setAcl path acl
	setquota -n|-b val path
	history 
	redo cmdno
	printwatches on|off
	delete path [version]
	sync path
	listquota path
	rmr path
	get path [watch]
	create [-s] [-e] path data acl
	addauth scheme auth
	quit 
	getAcl path
	close 
	connect host:port
复制代码

上記のリストに示すように、zkCliは、我々は3つのコマンドの代表を達成する、私たちのために多くのコマンドが用意されています。

1.接続ホスト:ポート

  这个命令其实是用来跟服务端建立会话的.
  为了避免跟socket建立tcp连接的connect方法相混淆, 我更愿意把它称作"login", 
  所以在实现的时候, 它对应的方法名也就是login
复制代码

[-S] [-e]パスデータACLを作成2。

   这个命令是用来创建zk的node节点的.
   其中包括持久性节点, 持久性顺序节点, 临时性节点, 临时性顺序节点等,
   还可以指定ACL权限
   
复制代码

3. LSパス[ウォッチ]

   ls命令就是列举zk某个路径下的所有子路径,
   在具体实现里, 我把这个命令叫做getChildren
复制代码

インサイドzookeep通信プロトコル、接続コマンド(ログイン)が他のすべてのコマンドのために必要な前提条件である。それ以来、クライアントとして、次のコマンド要求が受け入れられ、サーバーに処理されるように、サーバーとのセッションを確立する必要があります。

connectコマンドに加えて、他のすべてのコマンドは、実際には非常に異なっている。あなたはLSはコマンドの後にコマンドを、見つける理解し、作成されますように、このコマンドは他の事を達成するために非常に簡単です、彼らは単に通信プロトコルを理解する必要があります他のものは、コピーするモデルです。

もちろん、彼らの通信プロトコルを理解することは簡単な問題ではなく、また、各コマンドパケットの構造は全く同じではありません。実際には、コードのコード、それぞれの基本的な理解で消費すべてのエネルギーの70〜80パーセント上記コマンドメッセージ構造。

コードの実装

特定の実装を見て前に、プロジェクトの概要の構造を初めて目:

画像

  1. bean包
     封装了每个命令需要的字段参数, 在序列化报文时只需要序列化对应的bean即可. 同样, 在服务端返回内容时, 也只需要把报文序列化成对应的对象即可.
  2. factories包
      上面提到过, zk的每个命令的报文结构都是不一样的,所以在序列化和反序列化时, 对应到netty的codec也是不一样的.这个实现了一个codec静态方法工厂, 需要的时候直接从codec工厂拿对应的codec即可.
  3. registrys包
     其实就是一个缓存中心, 缓存了每个命令对应的requestId和codec, 在服务端返回时, 从这个缓存中心根据requestId拿到对应codec来进行反序列化
  4. utils包
     一些工具类, 不需要多解释
  
  5. zkcodec包
     每个命令对应的codec和handler实现
  6. NettyZkClient类
     就是本文要介绍的zk客户端了
  7. test    
     为了方便调试准备的单元测试, 先了解代码实现原理的可用直接从这个单元测试入手
     
复制代码

コードの構造を読んだ後、私たちは、各コマンドの特定の実装を見ていきます。

loginコマンド

ログインコマンドの通信パケットの構造を初めて目には、以下のように:

画像

簡単に言えば、我々は、オンラインで行うことができることを意味し、特定の理解がより深く検索し、各フィールドの意味を説明します。

 1. header
    上面提到, zk的每个报文都是header/content模式, 其中header占用4个字节, 表示接下来的content的长度(字节数)
 2. protocolVersion
    顾名思义, 这个字段表示协议的版本号,用4个字节表示. 这里我们写死为0即可(好浪费~~~)
 3. lastZxidSeen
    等下我们会看到, zk服务端每次的响应都会返回一个zxid.顾名思义, 这个结构就是表示客户端接收到最新的zxid.用8个字节表示. 由于login一般都是第一次跟服务端通讯, 所以这里也是写死为0即可
 4. timeout 
    login请求的目的是为了跟zk服务端建立会话, 这个参数表示的是会话的有效时间, 用四个字节表示
 5. senssionId
    会话ID, 用8个字节表示, 用于我们还没有建立会话,所以这个也是直接写死为0即可
 6. passwordLen
    用4个字节来表示接下来的密码的字节数
 7. password
    passwordLen个字节的密码, 用bytes[]表示
 8. readOnly
    boolean类型的,所以用一个字节表示即可
复制代码

後にメッセージ構造を知って、私たちはコード。まず、ZkLoginRequest Beanの定義を書き始めることができます。Javaの内部では、8バイトのタイプはロングで表すことができ、int型を表現できる4つのバイトは、文字列型は、非常に単純なことができ最終的な豆が定義するように、以下のようにバイト配列に変換します。

public class ZkLoginRequest implements Serializable {
  private Integer protocolVersion;
  private Long lastZxidSeen;
  private int timeout;
  private Long sessionId;
  private String password;
  private boolean readOnly = true;
    
}
复制代码

ZK我々は唯一のJavaオブジェクトを定義するように、通信では十分ではありません、またバイトのJavaオブジェクトに変換する必要があることは、サーバーを送信することができ、バイトベースである。そして、サービスはとても、パケットの形でのみヘッダ/コンテンツを受信するため、私たちは、ヘッダに行くためにそれを割り当てる、Javaの全体の標的配列の後のバイト数を計算する必要があります。

幸いなことに、これらの2つの作業は、私たち、私たちはそれを直接使用するための優れたツールを提供ネッティー。

JavaオブジェクトはByteBufにZkLoginCodecの変換を実現しました

ZkLoginCodecエンコードとデコードは、2つの部分を含み、前記ByteBuf網状にZkLoginRequestを変換するために、以下のようにされているエンコードを符号化するための要求を送信し、サーバをデコードするためのデコードに対応して

 @Override
 protected void encode(ChannelHandlerContext ctx, ZkLoginRequest msg, ByteBuf outByteBuf) throws Exception {
     outByteBuf.writeInt(msg.getProtocolVersion());
     outByteBuf.writeLong(msg.getLastZxidSeen());
     outByteBuf.writeInt(msg.getTimeout());
     outByteBuf.writeLong(msg.getSessionId());
     String passWord = msg.getPassword();
     SerializeUtils.writeStringToBuffer(passWord, outByteBuf);
     outByteBuf.writeBoolean(msg.isReadOnly());
 }
复制代码
直接内蔵のLengthFieldPrependerを使用してネッティー

網状の構成パラメータは、バイトの数は、ここで4バイト、ヘッダによって占め、それは4であることを示す、請求LengthFieldPrependerパケットは構造の形でヘッダ/コンテンツに変換することができる内蔵しました。

   // 编码器, 给报文加上一个4个字节大小的HEADER
   nioSocketChannel.pipeline().addLast(new LengthFieldPrepender(4))
   
复制代码

ZkLoginRequestこれら二つのコーデックのエンコード後の目標、ZKサーバーが正しくそのメッセージを解決することができます、そして予想外の何も起こらない場合、サーバは、私たちのためにセッションを確立するこのソケットは、私たちの応答メッセージ、メッセージを与えますセッションIDの構造は、次の応答が含まれます。

画像

あなたが見ることができ、私たちの要求パケットと応答メッセージがセッションIDを除いて、ほぼ同じである、他は基本的に私たちはここにそのまま返されます。応答メッセージの意味を説明するために、ここで行うことがそんなにダイレクトビューサーバから返された応答メッセージを解析する方法を見て。

私たちは、ヘッダとコンテンツを取得するには、内蔵デコーダネッティーを使用することができるように、我々は、リターン・メッセージは、ヘッダ/コンテンツの形で、チャートから見ることができます。

LengthFieldBasedFrameDecoderを使用してヘッダをスキップ

ネッティー学生に精通していないLengthFieldBasedFrameDecoderは、ちょうど私たちは、ヘッダーをスキップしたいことを知って、あなたは、応答メッセージの内容のみの部分を取得することができ、このクラスのパラメータがあまりにも多くの説明をしないの公式ウェブサイト、見てみることができ

nioSocketChannel.pipeline()
                        // 解码器, 将HEADER-CONTENT格式的报文解析成只包含CONTENT
        .addLast(ZkLoginHandler.LOGIN_LENGTH_FIELD_BASED_FRAME_DECODER,new LengthFieldBasedFrameDecoder(2048, 0, 4, 0, 4))
复制代码
ZkLoginResp逆にZkLoginCodecコンテンツシーケンス

これは、デコード部であるZkLoginCodec

   @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        ZkLoginResp zkLoginResp = new ZkLoginResp();
        zkLoginResp.setProtocolVersion(in.readInt());
        zkLoginResp.setTimeout(in.readInt());
        zkLoginResp.setSessionId(in.readLong());
        String password = SerializeUtils.readStringToBuffer(in);
        zkLoginResp.setPassword(password);
        zkLoginResp.setReadOnly(in.readBoolean());
        out.add(zkLoginResp);
    }
复制代码

このステップの後、私たちのクライアントが正常にサーバーとのセッションを確立しており、バックは喜んで他の要求を送信することができます

コマンドを作成します

オペコード和requestHeader

我々は、コマンド、二つの用語の最初の外観を作成する実装を開始する前に、二つの用語はまた、使用する必要がGetChildrenメソッドを達成するために次のコマンドをコマンド、待機を使用する必要性を作成していません。

  1. オペコード

オペコードを対応するクライアントの良い契約とサーバーのZK、オペコードの各コマンド対応、クライアントがコマンドオペコードは、このコマンドに対処する方法を知るために、この要求は、サーバーを持参しなければなりません送信します。たとえば、createコマンドは、1でありますGetChildrenメソッドは、次のコマンドを待つ達成するためには、オペコード8であります

  1. requestHeader

下に示すように、2つのフィールド、4バイトのそれぞれを含んrequestHeaderコンテンツのヘッダ又は一部にヘッダ/コンテンツと、このヘッダを混同しないでください。

画像

   1. xid, 通俗点理解的就是requestId, 客户端维护这个xid的唯一性, 服务端返回响应时会原封不动的返回这个xid,
   这样客户端就可以知道服务端返回的报文时对应哪个请求的了.毕竟socket通讯都是异步的.
   
   2. type
      这个更好理解, 就是上面的opcode
复制代码
コマンドメッセージ構造を作成します。

本当に、少し複雑なメッセージは、あなたがメッセージ構造createRequestを参照してくださいする前に、まず別の概念を理解する必要があり、コマンドを作成してあります:ACLの権限は、

以下の3点が含まれZooKeeperのACL権限:

  1. 身元を認証する仕組み4つの方法があります。
  2. 権限オブジェクトID
  3. 許可権限

特定が表示される場合があり、このブログをより明確にそれを置くために、。

この記事では、スキームを書いたidが「誰」で、許可(、であるCREATE、READ、WRITE、DELETE、ADMIN権限の5種類で、バイナリ11111に変換)31で、死んだ「世界」であります

次のようにZK ACLパケットの構造は次のようになります。

画像

  1. perms是permission的简写, 4个字节表示
  2. 因为scheme是用字符串表示的, 所以需要用4个字节表示scheme字符串的长度, 用schemelen个字节表示scheme
  3. id也是用字符串表示的, 跟scheme同理
复制代码

下に示すような構造requestHeaderとACLの理解、createRequestメッセージ構造は、より良い、理解されるであろう。

画像

1. requestHeader
   包含了xid和type
2. pathLen
   要创建的path的字符串长度
3. path
   要创建的path, 例如你想在zk上创建一个/dubbo节点, path就是"/path"
4. dataLen
   要创建的path下的data的长度
5. data
   要创建的path下的数据, 例如"192.168.99.101:2181"
6. aclListSize
   zk的ACL权限是list形式的,表示不同的权限控制维度
7. aclList
   aclListSize个ACL结构体
8. flags
   该节点的类型, 可以是持久性节点, 持久性顺序节点, 临时节点, 临时顺序节点4种
复制代码

次の仕事はほとんどログインされています。

  1. ZkCreateRequest ByteBufへの変換を達成ZkCreateCodec
  2. ByteBufはLengthFieldPrependerヘッダー/メッセージ構造の内容を使用して構築しました
  3. LengthFieldBasedFrameDecoderは、コンテンツサーバの応答からのコンテンツを使用して解析しました
  4. ZkCreateResponseに変換コンテンツにコンテンツByteBuf

前記createResパケット構造以下のように:

画像

1. xid
   这个就是createReq中requestHeader的xid
2. zxid
   这个可以跟login报文中的lastZxidSeen关联起来, 可以理解为服务端的xid
3. errcode
   errcode为0表示正常响应, 如果不为0,说明有异常出现, 后面的path字段也会为空
4. pathLen和path
   其实也是createReuest中的path和pathLen
复制代码

3. GetChildrenメソッドコマンド(LS路)

上記のコマンドを理解し、GetChildrenメソッドコマンドを作成することができますし、非常に簡単に理解している場合、2つだけのパケット構造が異なっているので、少し別のコーデックがあるでしょう。

getChildrenRequestメッセージ構造:

画像

応答の構造:

画像

コードを実行します。

すばやく簡単なzkClientを体験するには、ユニットテストから直接起動することができます。

public class ZkClientTest {
    @Test
    public void testZkClient() throws Exception {
        // NettyZkClient的构造方法里面会调用login() 跟服务端建立会话
        NettyZkClient nettyZkClient = new NettyZkClient(30000);

        // 创建一个临时顺序节点
        ZkCreateResponse createResponse = nettyZkClient.create("/as", 12312, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println(new Gson().toJson(createResponse));

        // 获取/下的所有子路径
        List<String> list =  nettyZkClient.getChildren("/");
        System.out.println(new Gson().toJson(list));

    }
}
复制代码

新しいコマンドを実装

異なるコマンド間のロジックがほとんど同じのため、私はいくつかの一般的なロジックは、あなたが他のコマンドを達成する必要がある場合は、その後、いくつかの簡単なタスクを実行する。たとえば、私が手を実装する、抽象化しているので、 pathコマンドは、その後、あなただけが必要です。

  1. メッセージgetコマンドの構造を決定するためのドキュメントを探すには、このステップは最も厄介です
  2. GetRequestクラス、継承RequestHeaderクラスを作成し、インターフェイスを実装ZkRequest
  3. 、GetRespクラス、継承AbstractZkResonseカテゴリを作成ZkResponseを実現
  4. GetRequestCodecを書いて、ByteBufとのGetRequest、ZkResponse変換を達成
  5. 変更ZkCodecFactoriesクラス、およびアソシエートのGetRequest GetRequestCodec

これは、新しいコマンドを達成することができる。もちろん、それは言及しなければならなかった、最初のステップは、最も厄介で、ワークロードの70〜80パーセントを費やす必要があります。

そういえば、それは?実際には、多くの方法がありますが、正式な文書が持っている可能性のある各コマンドパケットの構造を理解するために場所をいくつかが、求めることができる(しかし、私は見つけられません)。私のアプローチは最も簡単で、読むことですzkClientは既存のコードを、非常に視覚的に反映されないzkClientを既存の、コードをデバッグ(解析パケット)、及び捕捉法ので、サーバー側のコードを読み取るように組み合わされています。

ソース

ラインおよび完成zkClientクライアントの後、我々は十分ではない楽しいので、後でラインとRedisのクライアントとカフカプロデューサ/コンシューマのを発見しました。

行の後、長通信プロトコルの要件として、任意の網状で実施することができるように、実質的にC / Sアーキテクチャクライアント。したがって、これらのいくつかのクライアントは、プットgithubの上に、一緒に整理することを見出しました。

その後、弾性検索、mysqlの、ダボを達成するために続けていきたいので、クライアントは、(実際には、調査後に実現可能であるが、達成するための努力がなかったです)

最後に、githubのソースアドレスを添付:

github.com/NorthWard/a...

興味のある学生は、一般的な学習と共通の進捗状況を参照することができます。

おすすめ

転載: juejin.im/post/5dd296c0e51d4508182449a6