当然の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)。 } パブリック文字列セット(文字列キー、文字列値){ 文字列結果 = 接続。sendCommand( Protocol.buildRespByte(Protocol.Command.SET、key.getBytes()、value.getBytes()))。 戻り値の結果; } パブリック文字列GET(文字列キー){ 文字列結果= 接続。sendCommand( Protocol.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的创建和回收时间,在本例中更核心的功能是实现了线程不安全资源的管理。