研究Redisのクライアント実装BIO手(拒否Jedis)

  当然のRedisのでは、ほとんどの人は、このようなJedis、Redisson、レタスなど、クライアントを使用する準備が整いました。手書きのRedisクライアント側の試みBIOの道、探査や要約の問題点とそのため、本論文。

1、クラス手書きBIOを呼び出します

  BIO方法Redisのクライアントを使用して調製し、最初の接続、リモート接続BIOホストアドレス、ポートポート、ソケット入力および出力ストリームの確立を含む接続クラスを定義します。

  このような構成のA法、接続の初期化方法、及び要求送信方式の接続。

パブリック クラス接続{ 

    プライベート文字列のホスト。
    プライベート int型のポート。
    プライベートソケットソケット。
    プライベートInputStream InputStreamは、
    民間のOutputStreamのOutputStream。

    公共の接続(文字列のホスト、INT ポート){
         この .host = ホスト。
        この .port = ポート。
    } 

    パブリック ブールisConnection(){ 

        場合(ソケット!= NULL &&!socket.isClosed()&&!socket.isBound()&& socket.isConnected()){
             リターン ; 
        } 
        試みる{ 
            ソケット = 新しいソケット(ホスト、ポート)。
            inputStream = socket.getInputStream()。
            出力ストリーム = socket.getOutputStream()。
        } キャッチ(IOExceptionを電子){ 
            e.printStackTrace(); 
            リターン はfalse ; 
        } 
        を返す 
    } 

    パブリック文字列sendCommand(バイト[]コマンド){
         場合(isConnection()){
             試み{
                outputStream.write(コマンド);
                INT長さ= 0バイト []レスポンス=新しい バイト [1024 ];
                一方、((長さ= inputStream.read(応答))> 0){
                     戻り 、新たな文字列(応答、0、長さ)。
                } 
            } キャッチ(IOExceptionを電子){ 
                e.printStackTrace(); 
            } 
        } 
        戻り ヌル
    } 
}

  BIOでカテゴリを接続した後、あなたは要求を送信することができるし、次にビルド接続テストに要求を送信します:

パブリック クラスMainTest {
     公共 静的 ボイドメイン(文字列[]引数){ 

        stringコマンド =「セットアリ123456」
        接続の接続 = 新しい接続( "localhost"を、6379 ); 
        System.out.println(connection.sendCommand(command.getBytes()))。
  } 
}

  次の図の調査結果、要求は、呼び出しを返しませんでしたが、主な方法は、デバッグを通じて、終わっていないので、知ることができるInputStream.read(レスポンス)は次のような結果は、このようにmainメソッドをブロックし、返却されていないため)、このコードブロック呼び出しです図:

          

   Redisの要求を認識し、復帰を行わないよう契約は、すべてのアクセスのRedisに従っていないので、要求は、要求コマンド=「セットアリ123456」を送信した後、任意のプロトコルに基づいているため、実際には、理由があります。RESP Redisのプロトコルを用い、RESPプロトコルはRedisの1.2で導入され、それはサーバとの標準的な方法のRedis 2.0 Redisの通信となります。

  単純な文字列、エラー、整数、文字列、および大容量アレイ:RESPは、実際には、プロトコル・データ・タイプの次のシーケンスをサポートしています。

  要求としてRESPのRedis - 応答プロトコルを以下のように:

  • クライアントは、大容量の文字列としてサーバーRedisのRESP・アレイにコマンドを送信します。
  • サーバーは、達成するために、コマンドの種類に応じて1つのRESPで応答します。

  RESPでは、いくつかのデータの種類は、の最初のバイトに依存します。

  • 以下のために、単純な文字列、答えの最初のバイトは、「+」であります
  • 以下のために、エラー、応答の最初のバイトは、「 - 」
  • 以下のために、整数、最初のバイトの答えは「:」
  • 以下のためにバルクの文字列、最初のバイトの答えは「$」
  • 以下のために、配列、応答の最初のバイトは、「あります  *

  さらに、RESPバルク文字列を用いてもよいし、後で指定されるように、特定のバリアント配列は、NULL値を表しました。RESPの異なる部分、常にプロトコル"\ rを\ n"は(CRLF ) 終了。詳細については、https://redis.io/topics/protocolを

2、手書きのRESPプロトコルクラス

  プロトコルクラスプロトコル、本実施例十分達成されないが、SETが簡単に達成される定義、およびGET要求の内容を解析します。

パブリック クラスプロトコル{ 

    公共の 静的な 最終文字列ダラー= "$" ;
     公共の 静的な 最終文字列ALLERSTIC = "*" ;
     公共の 静的な 最終文字列CRLF = "\ R&LT \ N-" ; 
  
  // SET要求のAnt 7777 SET 
  // * 3 \。 R \ nは長さ3の配列
  // $ 3 \ R \ nは長さ3の最初の列
  // SET \ R \ nはSETの最初の文字列
  // $ 3 \ R \ n個の第2文字列の長さである。3
  // Antの\ R&LT \ n-2番目の文字列のAnt 
  // ストリングの$ 4 \ R&LT \ n型第3の長さ。4
  // \ R&LT \ n-3番目の文字列7777 7777 
    公共の 静的 バイト [] buildRespByte(commandコマンド、バイト[] ...バイト){ 
        StringBuilderのStringBuilderの = 新規のStringBuilder(); 
        stringBuilder.append(ALLERSTIC).append(bytes.length +1 ).append(CRLF)。

     // 封装方法SET、GET         
     stringBuilder.append(ダラー).append(command.name()の長さ()。)追記(CRLF)。
stringBuilder.append(command.name())アペンド(CRLF)。 // 封装参数 ためバイト[]引数:バイト){ stringBuilder.append(ダラー).append(arg.length).append(CRLF)。 stringBuilder.append(新しい文字列(引数)).append(CRLF)。 } リターン stringBuilder.toStringを()GetBytesメソッド()。 } パブリック 列挙コマンド{ SET、GET } }

  その後、コールクライアントコールのパッケージセットを作成して、メソッドを取得します:

パブリック クラスSelfRedisClient { 

    プライベート接続接続。

    公共 SelfRedisClient(文字列ホスト、INT IP){ 
        接続 = 新しい接続(ホスト、IP)。
    } 

    パブリック文字列セット(文字列キー、文字列値){ 
        文字列結果 = 接続。sendCommandProtocol.buildRespByte(Protocol.Command.SET、key.getBytes()、value.getBytes()))。
        戻り値の結果; 
    } 

    パブリック文字列GET(文字列キー){ 
        文字列結果= 接続。sendCommandProtocol.buildRespByte(Protocol.Command.GET、key.getBytes()))。
        戻り値の結果; 
    } 
}

  そして、メインメソッドを呼び出します。

パブリック クラスMainTest {
     公共 静的 ボイドメイン(文字列[]引数){ 
        SelfRedisClient selfRedisClient = 新しい SelfRedisClient( "ローカルホスト"、6379 )。
        System.out.println(selfRedisClient.set( "アリ"、 "123456" )); 
        System.out.println(selfRedisClient.get( "アリ" )); 
    } 
}

  結果はもちろん、私たちは分析の結果を返すために契約を使用していない、通常の復帰で見ることができます。

      

 図3に示すように、マルチスレッドRedisの要求の使用

  上記の例では、マルチスレッド状況の場合とシングルスレッドアクセス、次に起こるでテストされています。次に、我々は複数のスレッドを使用して、スレッドプールを構築し、次のようにClientRunnableを構築するためにRedisの要求にしよう:

パブリック クラス ClientRunnable 実装Runnableを{ 

    プライベートSelfRedisClient selfRedisClient。
    プライベートString値。

    公共ClientRunnable(SelfRedisClient selfRedisClient、文字列値){
         この .selfRedisClient = selfRedisClient。
        この .VALUE = 値; 
    } 
    @Override 
    公共 ボイドラン(){
         selfRedisClient.set( "ANT" 値)。
    } 
}

  次のように主な方法は次のとおりです。

パブリック クラスMainTest {
     公共 静的 ボイドメイン(文字列[]引数){ 
        SelfRedisClient selfRedisClient = 新しい SelfRedisClient( "ローカルホスト"、6379 )。
        ExecutorServiceのプール = エグゼキュー。newCachedThreadPool();
        INT ; I <20 I ++ iが0 = {)
            プール。実行 ClientRunnable(selfRedisClient、 "値" + I)); 
        } 
    } 
}

  そして、設定された方法でコンソールに出力を上げます:

パブリック文字列セット(文字列キー、文字列値){ 
    文字列結果 = connection.sendCommand(
            Protocol.buildRespByte(Protocol.Command.SET、key.getBytes()、value.getBytes()))。System.out.println(
     "スレッド名:" +にThread.currentThread()のgetName()+ "[結果]:" 
            + result.replace( "\ R \ n"、 "")+ "[値]:" + 値)。
    戻り値の結果; 
}

  次のような結果を表示します。

        

   見つかりリターンの結果だけでなく、さらに多くの時間が復帰へのOK 2つのRedisのサービスが行われているが、実行の主な方法は終了していません。なぜソケットの下でマルチスレッドで要求を送信しながら、複数のスレッドが、ソケットにアクセスするとき、スレッドセーフであり、そして、要求の結果が蓄積されて返すため場合は、その後、完全にスレッドを取得し、残りは、要求を送信しましたスレッドが戻るのを待ってブロックしますが、プログラムを続行できませんので、流れを通す前に傍受されています。

            

   だから今我々は接続を管理するためのスレッドプールを必要とし、単一の接続を使用して、各スレッドは、スレッドの接続は、スレッドが呼び出しを完了するまでブロックされたキューで待機して取得していない、との接続は、スレッドプールにバックをリリースブロックされました電話をかけるために続行する前にスレッド。図は次のとおりです。

          

4、实现Connection的线程池管理

  首先实现一个阻塞队列用于管理特定数量的Connection,当有Connection使用时就返回Connection,用完Connection后就进行归还。

public class RedisClientPool {

    private LinkedBlockingQueue<SelfRedisClient> linkedBlockingQueue;

    public RedisClientPool(String host,int port ,int connectionCount){
        this.linkedBlockingQueue = new LinkedBlockingQueue<SelfRedisClient>(connectionCount);
        for(int i=0;i<connectionCount;i++){
            SelfRedisClient selfRedisClient = new SelfRedisClient(host,port);
            linkedBlockingQueue.add(selfRedisClient);
        }
    }

    public SelfRedisClient getClient(){
        try{
            return linkedBlockingQueue.take();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return null;
    }

    public void returnClient(SelfRedisClient selfRedisClient) {
        if(selfRedisClient != null){
            linkedBlockingQueue.add(selfRedisClient);
        }
    }
}

  修改ClientRunnable方法,改为从线程池获取Connection进行请求调用:

public class ClientRunnable implements Runnable {

    private RedisClientPool redisClientPool;
    private String value;

    public ClientRunnable(RedisClientPool redisClientPool, String value) {
        this.redisClientPool = redisClientPool;
        this.value = value;
    }

    @Override
    public void run() {
        // 执行前先去管理Connection的阻塞队列中获取封装了Connection的SelfRedisClient
        SelfRedisClient selfRedisClient = redisClientPool.getClient();
        selfRedisClient.set("ant", value);
        // 使用完后进行归还client
        redisClientPool.returnClient(selfRedisClient);
    }
}

  使用Main方法进行请求调用:

public class MainTest {
    public static void main(String[] args) {
        RedisClientPool redisClientPool = new RedisClientPool("localhost",6379,5);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for(int i=0;i<10;i++){
            executorService.execute(new ClientRunnable(redisClientPool,"value"+i));
        }
    }
}

  查看执行结果:    

          

   可以知道成功返回了所有的请求调用,最后也是线程9成功将value值修改为value8。

  因此,可以发现使用一个阻塞队列对Connection资源进行管理不仅近能节省Connection的创建和回收时间,在本例中更核心的功能是实现了线程不安全资源的管理。  

おすすめ

転載: www.cnblogs.com/jing99/p/11854530.html